免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書(shū)等14項(xiàng)超值服

開(kāi)通VIP
Django實(shí)戰(zhàn)

1.環(huán)境搭建和創(chuàng)建項(xiàng)目

1.環(huán)境搭建

每當(dāng)我們開(kāi)始一個(gè)新項(xiàng)目的時(shí)候,通常都會(huì)搭建一個(gè)全新、獨(dú)立、隔離的項(xiàng)目環(huán)境,這樣做的好處自然不必多說(shuō)。有很多種建立項(xiàng)目虛擬環(huán)境的工具,使用比較普遍的是Python中的virtualenv。安裝好virtualenv工具后,進(jìn)入想要放置的項(xiàng)目文件夾,建立一個(gè)虛擬環(huán)境,激活環(huán)境,安裝django。

virtualenv -p python3 venv #創(chuàng)建虛擬環(huán)境source venv/bin/activate #激活虛擬環(huán)境pip install django==1.11.7 #安裝django

2.創(chuàng)建項(xiàng)目

在當(dāng)前虛擬環(huán)境,創(chuàng)建django項(xiàng)目,完成后會(huì)生成login_site項(xiàng)目文件夾,進(jìn)入,運(yùn)行django內(nèi)置服務(wù)器,在本機(jī)的瀏覽器中訪問(wèn)http://127.0.0.1:8000/,這時(shí)我們的django服務(wù)已經(jīng)跑起來(lái)了。

django-admin startproject login_sitecd login_sitepython manage.py startapp login #創(chuàng)建apppython manage.py runserver

Django默認(rèn)使用美國(guó)時(shí)間和英語(yǔ),我們可以將時(shí)間和語(yǔ)言更改一下。配置文件主要在setting.py中,

#beforeLANGUAGE_CODE = 'en-us'TIME_ZONE = 'UTC'USE_I18N = TrueUSE_L10N = TrueUSE_TZ = True#afterLANGUAGE_CODE = 'zh-hans'TIME_ZONE = 'Asia/Shanghai'USE_I18N = TrueUSE_L10N = TrueUSE_TZ = True時(shí)間/語(yǔ)言設(shè)置

2.數(shù)據(jù)庫(kù)模型設(shè)計(jì)

2.1.數(shù)據(jù)庫(kù)模型設(shè)計(jì)

作為一個(gè)用戶登錄和注冊(cè)項(xiàng)目,需要保存的都是各種用戶的相關(guān)信息。很顯然,我們至少需要一張用戶表User,在用戶表里需要保存下面的信息:

  • 用戶名
  • 密碼
  • 郵箱地址
  • 性別
  • 創(chuàng)建時(shí)間

進(jìn)入login/models.py文件,代碼如下,

from django.db import models# Create your models here.class User(models.Model):    gender = (        ('male', "男"),        ('female', "女"),    )    name = models.CharField(max_length=128, unique=True)    password = models.CharField(max_length=256)    email = models.EmailField(unique=True)    sex = models.CharField(max_length=32, choices=gender, default="男")    c_time = models.DateTimeField(auto_now_add=True)    def __str__(self):        return self.name    class Meta:        ordering = ["c_time"]        verbose_name = "用戶"        verbose_name_plural = "用戶"

各字段含義:

  • name必填,最長(zhǎng)不超過(guò)128個(gè)字符,并且唯一,也就是不能有相同姓名;
  • password必填,最長(zhǎng)不超過(guò)256個(gè)字符(實(shí)際可能不需要這么長(zhǎng));
  • email使用Django內(nèi)置的郵箱類型,并且唯一;
  • 性別使用了一個(gè)choice,只能選擇男或者女,默認(rèn)為男;
  • 使用__str__幫助人性化顯示對(duì)象信息;
  • 元數(shù)據(jù)里定義用戶按創(chuàng)建時(shí)間的反序排列,也就是最近的最先顯示;

注意:這里的用戶名指的是網(wǎng)絡(luò)上注冊(cè)的用戶名,不要等同于現(xiàn)實(shí)中的真實(shí)姓名,所以采用了唯一機(jī)制。如果是現(xiàn)實(shí)中可以重復(fù)的人名,那肯定是不能設(shè)置unique的。

2.2.數(shù)據(jù)庫(kù)設(shè)置(Mysql)

在settings.py修改,一定要加上前面的導(dǎo)入?;蚴窃趇nit.py里面導(dǎo)入pymysql模塊。

import pymysql         # 一定要添加這兩行!           pymysql.install_as_MySQLdb()DATABASES = {    'default': {        'ENGINE': 'django.db.backends.mysql',        'NAME': 'loginsys',        #數(shù)據(jù)庫(kù)名字        'USER': 'root',          #賬號(hào)        'PASSWORD': '123456',      #密碼        'HOST': '127.0.0.1',    #IP        'PORT': '3306',                   #端口    }}

2.3.注冊(cè)APP

INSTALLED_APPS = [    'django.contrib.admin',    'django.contrib.auth',    'django.contrib.contenttypes',    'django.contrib.sessions',    'django.contrib.messages',    'django.contrib.staticfiles',    'login',]

數(shù)據(jù)庫(kù)遷移,每次models.py有更改,都要應(yīng)用以下命令。

python manage.py makemigrationspython manage.py migrate

3.admin后臺(tái)

3.1.在admin中注冊(cè)模型

# login/admin.pyfrom django.contrib import adminfrom . import modelsadmin.site.register(models.User)

3.2.創(chuàng)建超級(jí)管理員

python manage.py createsuperuser

3.3進(jìn)入admin后臺(tái)

創(chuàng)建好超級(jí)管理員后,就可以啟動(dòng)我們的開(kāi)發(fā)服務(wù)器了,然后在瀏覽器中訪問(wèn)http://127.0.0.1:8000/admin/地址

注意,圖中下方的認(rèn)證和授權(quán)是admin應(yīng)用自身的賬戶管理,上面的LOGIN欄目才是我們自己創(chuàng)建的login應(yīng)用所對(duì)應(yīng)的User模型。

增加測(cè)試用戶,

4.URL路由和視圖設(shè)置

4.1.路由設(shè)計(jì)

初步設(shè)想需要下面的四個(gè)URL:

URL

視圖

模板

說(shuō)明

/index/

login.views.index()

index.html

主頁(yè)

/login/

login.views.login()

login.html

登錄

/register/

login.views.register()

register.html

注冊(cè)

/logout/

login.views.logout()

無(wú)需專門的頁(yè)面

登出

考慮到登錄系統(tǒng)屬于站點(diǎn)的一級(jí)功能,為了直觀和更易于接受,這里沒(méi)有采用二級(jí)路由的方式,而是在根路由下直接編寫(xiě)路由條目,同樣也沒(méi)有使用反向解析名(name參數(shù))。

# login_sys/urls.pyfrom django.conf.urls import urlfrom django.contrib import adminfrom login import viewsurlpatterns = [    url(r'^admin/', admin.site.urls),    url(r'^index/', views.index),    url(r'^login/', views.login),    url(r'^register/', views.register),    url(r'^logout/', views.logout),]

4.2.視圖初構(gòu)

路由寫(xiě)好了,就進(jìn)入login/views.py文件編寫(xiě)視圖的框架,代碼如下:

# login/views.pyfrom django.shortcuts import render,redirectdef index(request):    pass    return render(request,'login/index.html')def login(request):    pass    return render(request,'login/login.html')def register(request):    pass    return render(request,'login/register.html')def logout(request):    pass    return redirect('/index/')

我們先不著急完成視圖內(nèi)部的具體細(xì)節(jié),而是把框架先搭建起來(lái)。

4.3.創(chuàng)建HTML頁(yè)面文件

在項(xiàng)目根路徑的login目錄中創(chuàng)建一個(gè)templates目錄,再在templates目錄里創(chuàng)建一個(gè)login目錄

login/templates/login目錄中創(chuàng)建三個(gè)文件index.html、login.html以及register.html ,并寫(xiě)入如下的代碼:

{#login/templates/login/index.html#}<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>首頁(yè)</title></head><body><h1>首頁(yè)</h1></body></html>{#login/templates/login/login.html#}<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>登錄</title></head><body><h1>登錄頁(yè)面</h1></body></html>{#login/templates/login/register.html#}<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>注冊(cè)</title></head><body><h1>注冊(cè)頁(yè)面</h1></body></html>

保存后,運(yùn)行服務(wù),試試訪問(wèn)下以上路由頁(yè)面。

5.前端頁(yè)面設(shè)計(jì)

5.1.原生HTML頁(yè)面

刪除原來(lái)的login.html文件中的內(nèi)容,寫(xiě)入下面的代碼:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>登錄</title></head><body>     <div style="margin: 15% 40%;">        <h1>歡迎登錄!</h1>       <form action="/login/" method="post">            <p>                <label for="id_username">用戶名:</label>                <input type="text" id="id_username" name="username" placeholder="用戶名" autofocus required />            </p>                      <p>                <label for="id_password">密碼:</label>                <input type="password" id="id_password" placeholder="密碼" name="password" required >            </p>            <input type="submit" value="確定">        </form>    </div></body></html>

保存,啟動(dòng)服務(wù)器,可以看到如下圖的頁(yè)面:

5.2.引入Bootstrap

下載生產(chǎn)環(huán)境下的bootstrap,在項(xiàng)目根目錄(manage.py同級(jí))下新建一個(gè)static目錄,并將解壓后的bootstrap-3.3.7-dist目錄,整體拷貝到static目錄中,如下圖所示:

由于Bootstrap依賴JQuery,所以我們需要提前下載并引入JQuery,在static目錄下,新建一個(gè)css和js目錄,作為以后的樣式文件和js文件的存放地,將我們的jquery文件拷貝到static/js目錄下。

然后打開(kāi)項(xiàng)目的settings文件,在最下面添加配置,用于指定靜態(tài)文件的搜索目錄:

STATIC_URL = '/static/'STATICFILES_DIRS = [    os.path.join(BASE_DIR, "static"),]

5.3.創(chuàng)建base.html模板

既然要將前端頁(yè)面做得像個(gè)樣子,那么就不能和前面一樣,每個(gè)頁(yè)面都各寫(xiě)各的,單打獨(dú)斗。一個(gè)網(wǎng)站有自己的統(tǒng)一風(fēng)格和公用部分,可以把這部分內(nèi)容集中到一個(gè)基礎(chǔ)模板base.html中?,F(xiàn)在,在根目錄下的templates中新建一個(gè)base.html文件用作站點(diǎn)的基礎(chǔ)模板。

在Bootstrap文檔中,為我們提供了一個(gè)非常簡(jiǎn)單而又實(shí)用的基本模板,代碼如下:

<!DOCTYPE html><html lang="zh-CN">  <head>    <meta charset="utf-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=device-width, initial-scale=1">    <!-- 上述3個(gè)meta標(biāo)簽*必須*放在最前面,任何其他內(nèi)容都*必須*跟隨其后! -->    <title>Bootstrap 101 Template</title>    <!-- Bootstrap -->    <link href="css/bootstrap.min.css" rel="stylesheet">    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->    <!--[if lt IE 9]>      <script src="https://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script>      <script src="https://cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script>    <![endif]-->  </head>  <body>    <h1>你好,世界!</h1>    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->    <script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>    <!-- Include all compiled plugins (below), or include individual files as needed -->    <script src="js/bootstrap.min.js"></script>  </body></html>

將它整體拷貝到base.html文件中。

5.4.創(chuàng)建頁(yè)面導(dǎo)航條

Bootstrap提供了現(xiàn)成的導(dǎo)航條組件

<nav class="navbar navbar-default">  <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="#">Brand</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="#">Link <span class="sr-only">(current)</span></a></li>        <li><a href="#">Link</a></li>        <li class="dropdown">          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <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">        <li><a href="#">Link</a></li>        <li class="dropdown">          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <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>          </ul>        </li>      </ul>    </div><!-- /.navbar-collapse -->  </div><!-- /.container-fluid --></nav>

其中有一些部分,比如搜索框是我們目前還不需要的,需要將多余的內(nèi)容裁剪掉。同時(shí),有一些名稱和url地址等需要按我們的實(shí)際內(nèi)容修改。最終導(dǎo)航條的代碼如下:

<nav class="navbar navbar-default">      <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="#my-nav" aria-expanded="false">            <span class="sr-only">切換導(dǎo)航條</span>            <span class="icon-bar"></span>            <span class="icon-bar"></span>            <span class="icon-bar"></span>          </button>          <a class="navbar-brand" href="#">Mysite</a>        </div>        <!-- Collect the nav links, forms, and other content for toggling -->        <div class="collapse navbar-collapse" id="my-nav">          <ul class="nav navbar-nav">            <li class="active"><a href="/index/">主頁(yè)</a></li>          </ul>          <ul class="nav navbar-nav navbar-right">            <li><a href="/login/">登錄</a></li>            <li><a href="/register/">注冊(cè)</a></li>          </ul>        </div><!-- /.navbar-collapse -->      </div><!-- /.container-fluid -->    </nav>

5.5.使用Bootstrap靜態(tài)文件

{% static '相對(duì)路徑' %}這個(gè)Django為我們提供的靜態(tài)文件加載方法,可以將頁(yè)面與靜態(tài)文件鏈接起來(lái)

{% load staticfiles %}<!DOCTYPE html><html lang="zh-CN">  <head>    <meta charset="utf-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=device-width, initial-scale=1">    <!-- 上述3個(gè)meta標(biāo)簽*必須*放在最前面,任何其他內(nèi)容都*必須*跟隨其后! -->    <title>{% block title %}base{% endblock %}</title>    <!-- Bootstrap -->    <link href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}" rel="stylesheet">    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->    <!--[if lt IE 9]>      <script src="https://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script>      <script src="https://cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script>    <![endif]-->    {% block css %}{% endblock %}  </head>  <body>    <nav class="navbar navbar-default">      <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="#my-nav" aria-expanded="false">            <span class="sr-only">切換導(dǎo)航條</span>            <span class="icon-bar"></span>            <span class="icon-bar"></span>            <span class="icon-bar"></span>          </button>          <a class="navbar-brand" href="#">Mysite</a>        </div>        <!-- Collect the nav links, forms, and other content for toggling -->        <div class="collapse navbar-collapse" id="my-nav">          <ul class="nav navbar-nav">            <li class="active"><a href="/index/">主頁(yè)</a></li>          </ul>          <ul class="nav navbar-nav navbar-right">            <li><a href="/login/">登錄</a></li>            <li><a href="/register/">注冊(cè)</a></li>          </ul>        </div><!-- /.navbar-collapse -->      </div><!-- /.container-fluid -->    </nav>    {% block content %}{% endblock %}    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->    <script src="{% static 'js/jquery-3.3.1.js' %}"></script>    <!-- Include all compiled plugins (below), or include individual files as needed -->    <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>  </body></html>

簡(jiǎn)要說(shuō)明:

  • 通過(guò)頁(yè)面頂端的{% load staticfiles %}加載后,才可以使用static方法;
  • 通過(guò){% block title %}base{% endblock %},設(shè)置了一個(gè)動(dòng)態(tài)的頁(yè)面title塊;
  • 通過(guò){% block css %}{% endblock %},設(shè)置了一個(gè)動(dòng)態(tài)的css加載塊;
  • 通過(guò){% block content %}{% endblock %},為具體頁(yè)面的主體內(nèi)容留下接口;
  • 通過(guò){% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}將樣式文件指向了我們的實(shí)際靜態(tài)文件,下面的js腳本也是同樣的道理。

看下效果

5.6.設(shè)計(jì)登錄頁(yè)面

Bootstarp提供了一個(gè)基本的表單樣式,代碼如下:

<form>  <div class="form-group">    <label for="exampleInputEmail1">Email address</label>    <input type="email" class="form-control" id="exampleInputEmail1" placeholder="Email">  </div>  <div class="form-group">    <label for="exampleInputPassword1">Password</label>    <input type="password" class="form-control" id="exampleInputPassword1" placeholder="Password">  </div>  <div class="form-group">    <label for="exampleInputFile">File input</label>    <input type="file" id="exampleInputFile">    <p class="help-block">Example block-level help text here.</p>  </div>  <div class="checkbox">    <label>      <input type="checkbox"> Check me out    </label>  </div>  <button type="submit" class="btn btn-default">Submit</button></form>

我們結(jié)合Bootstrap和前面自己寫(xiě)的form表單,修改login/templates/login/login.html成符合項(xiàng)目要求的樣子:

{% extends 'login/base.html' %}{% load staticfiles %}{% block title %}登錄{% endblock %}{% block css %}    <link rel="stylesheet" href="{% static 'css/login.css' %}">{% endblock %}{% block content %}    <div class="container">        <div class="col-md-4 col-md-offset-4">          <form class='form-login' action="/login/" method="post">              <h2 class="text-center">歡迎登錄</h2>              <div class="form-group">                <label for="id_username">用戶名:</label>                <input type="text" name='username' class="form-control" id="id_username" placeholder="Username" autofocus required>              </div>              <div class="form-group">                <label for="id_password">密碼:</label>                <input type="password" name='password' class="form-control" id="id_password" placeholder="Password" required>              </div>              <button type="reset" class="btn btn-default pull-left">重置</button>              <button type="submit" class="btn btn-primary pull-right">提交</button>          </form>        </div>    </div> <!-- /container -->{% endblock %}

說(shuō)明:

  • 通過(guò){% extends 'base.html' %}繼承了‘base.html’模板的內(nèi)容;
  • 通過(guò){% block title %}登錄{% endblock %}設(shè)置了專門的title;
  • 通過(guò)block css引入了針對(duì)性的login.css樣式文件;
  • 主體內(nèi)容定義在block content內(nèi)部
  • 添加了一個(gè)重置按鈕。

static/css目錄中新建一個(gè)login.css樣式文件,這里簡(jiǎn)單地寫(xiě)了點(diǎn)樣式,

body {  background-color: #eee;}.form-login {  max-width: 330px;  padding: 15px;  margin: 0 auto;}.form-login .form-control {  position: relative;  height: auto;  -webkit-box-sizing: border-box;     -moz-box-sizing: border-box;          box-sizing: border-box;  padding: 10px;  font-size: 16px;}.form-login .form-control:focus {  z-index: 2;}.form-login input[type="text"] {  margin-bottom: -1px;  border-bottom-right-radius: 0;  border-bottom-left-radius: 0;}.form-login input[type="password"] {  margin-bottom: 10px;  border-top-left-radius: 0;  border-top-right-radius: 0;}

最后效果:

6、登錄視圖

6.1.登錄視圖

根據(jù)我們?cè)诼酚芍械脑O(shè)計(jì),用戶通過(guò)login.html中的表單填寫(xiě)用戶名和密碼,并以POST的方式發(fā)送到服務(wù)器的/login/地址。服務(wù)器通過(guò)login/views.py中的login()視圖函數(shù),接收并處理這一請(qǐng)求。

我們可以通過(guò)下面的方法接收和處理請(qǐng)求:

def login(request):    if request.method == "POST":        username = request.POST.get('username')        password = request.POST.get('password')        print(username, password)        return redirect('/index/')    return render(request, 'login/login.html')

還需要在前端頁(yè)面的form表單內(nèi)添加一個(gè){% csrf_token %}標(biāo)簽,CSRF(Cross-site request forgery)跨站請(qǐng)求偽造,是一種常見(jiàn)的網(wǎng)絡(luò)攻擊手段,具體原理和技術(shù)內(nèi)容請(qǐng)自行百科。Django自帶對(duì)許多常見(jiàn)攻擊手段的防御機(jī)制,CSRF就是其中一種,還有XSS、SQL注入等。

<form class='form-login' action="/login/" method="post">  {% csrf_token %}  <h2 class="text-center">歡迎登錄</h2>  <div class="form-group">  ......</form>

這個(gè)標(biāo)簽必須放在form表單內(nèi)部,但是內(nèi)部的位置可以隨意。

重新刷新login頁(yè)面,確保csrf的標(biāo)簽生效,然后再次輸入內(nèi)容并提交。瀏覽器頁(yè)面跳轉(zhuǎn)到了首頁(yè)。

6.2.數(shù)據(jù)驗(yàn)證

通過(guò)唯一的用戶名,使用Django的ORM去數(shù)據(jù)庫(kù)中查詢用戶數(shù)據(jù),如果有匹配項(xiàng),則進(jìn)行密碼對(duì)比,如果沒(méi)有匹配項(xiàng),說(shuō)明用戶名不存在。如果密碼對(duì)比錯(cuò)誤,說(shuō)明密碼不正確。

def login(request):    if request.method == "POST":        username = request.POST.get('username', None)        password = request.POST.get('password', None)        if username and password:  # 確保用戶名和密碼都不為空            username = username.strip()            # 用戶名字符合法性驗(yàn)證            # 密碼長(zhǎng)度驗(yàn)證            # 更多的其它驗(yàn)證.....            try:                user = models.User.objects.get(name=username)            except:                return render(request, 'login/login.html')            if user.password == password:                return redirect('/index/')    return render(request, 'login/login.html')

6.3.添加提示信息

上面的代碼還缺少很重要的一部分內(nèi)容,提示信息!無(wú)論是登錄成功還是失敗,用戶都沒(méi)有得到任何提示信息,這顯然是不行的。

修改一下login視圖:

def login(request):    if request.method == "POST":        username = request.POST.get('username', None)        password = request.POST.get('password', None)        message = "所有字段都必須填寫(xiě)!"        if username and password:  # 確保用戶名和密碼都不為空            username = username.strip()            # 用戶名字符合法性驗(yàn)證            # 密碼長(zhǎng)度驗(yàn)證            # 更多的其它驗(yàn)證.....            try:                user = models.User.objects.get(name=username)                if user.password == password:                    return redirect('/index/')                else:                    message = "密碼不正確!"            except:                message = "用戶名不存在!"        return render(request, 'login/login.html', {"message": message})    return render(request, 'login/login.html')

增加了message變量,用于保存提示信息。當(dāng)有錯(cuò)誤信息的時(shí)候,將錯(cuò)誤信息打包成一個(gè)字典,然后作為第三個(gè)參數(shù)提供給render()方法。這個(gè)數(shù)據(jù)字典在渲染模板的時(shí)候會(huì)傳遞到模板里供你調(diào)用。

為了在前端頁(yè)面顯示信息,還需要對(duì)login.html進(jìn)行修改:

{% extends 'login/base.html' %}{% load staticfiles %}{% block title %}登錄{% endblock %}{% block css %}    <link rel="stylesheet" href="{% static 'css/login.css' %}">{% endblock %}{% block content %}    <div class="container">        <div class="col-md-4 col-md-offset-4">            <form class='form-login' action="/login/" method="post">                {% if message %}                    <div class="alert alert-warning">{{ message }}</div>                {% endif %}                {% csrf_token %}                <h2 class="text-center">歡迎登錄</h2>                <div class="form-group">                    <label for="id_username">用戶名:</label>                    <input type="text" name='username' class="form-control" id="id_username" placeholder="Username"                           autofocus required>                </div>                <div class="form-group">                    <label for="id_password">密碼:</label>                    <input type="password" name='password' class="form-control" id="id_password" placeholder="Password"                           required>                </div>                <button type="reset" class="btn btn-default pull-left">重置</button>                <button type="submit" class="btn btn-primary pull-right">提交</button>            </form>        </div>    </div> <!-- /container -->{% endblock %}

index.html主頁(yè)模板也修改一下,刪除原有內(nèi)容,添加下面的代碼:

{#login/templates/login/index.html#}{% extends 'login/base.html' %}{% block title %}主頁(yè){% endblock %}{% block content %}    <h1>歡迎回來(lái)!</h1>{% endblock %}

7.Django表單

Django的表單給我們提供了下面三個(gè)主要功能:

  • 準(zhǔn)備和重構(gòu)數(shù)據(jù)用于頁(yè)面渲染;
  • 為數(shù)據(jù)創(chuàng)建HTML表單元素;
  • 接收和處理用戶從表單發(fā)送過(guò)來(lái)的數(shù)據(jù)

編寫(xiě)Django的form表單,非常類似我們?cè)谀P拖到y(tǒng)里編寫(xiě)一個(gè)模型。在模型中,一個(gè)字段代表數(shù)據(jù)表的一列,而form表單中的一個(gè)字段代表<form>中的一個(gè)<input>元素。

7.1.創(chuàng)建表單模型

from django import formsclass UserForm(forms.Form):    username = forms.CharField(label="用戶名", max_length=128)    password = forms.CharField(label="密碼", max_length=256, widget=forms.PasswordInput)

說(shuō)明:

  • 要先導(dǎo)入forms模塊
  • 所有的表單類都要繼承forms.Form類
  • 每個(gè)表單字段都有自己的字段類型比如CharField,它們分別對(duì)應(yīng)一種HTML語(yǔ)言中<form>內(nèi)的一個(gè)input元素。這一點(diǎn)和Django模型系統(tǒng)的設(shè)計(jì)非常相似。
  • label參數(shù)用于設(shè)置<label>標(biāo)簽
  • max_length限制字段輸入的最大長(zhǎng)度。它同時(shí)起到兩個(gè)作用,一是在瀏覽器頁(yè)面限制用戶輸入不可超過(guò)字符數(shù),二是在后端服務(wù)器驗(yàn)證用戶輸入的長(zhǎng)度也不可超過(guò)。
  • widget=forms.PasswordInput用于指定該字段在form表單里表現(xiàn)為<input type='password' />,也就是密碼輸入框。

7.2.修改視圖

使用了Django的表單后,就要在視圖中進(jìn)行相應(yīng)的修改:

# login/views.pyfrom django.shortcuts import render,redirectfrom . import modelsfrom .forms import UserFormdef index(request):    pass    return render(request,'login/index.html')def login(request):    if request.method == "POST":        login_form = UserForm(request.POST)        message = "請(qǐng)檢查填寫(xiě)的內(nèi)容!"        if login_form.is_valid():            username = login_form.cleaned_data['username']            password = login_form.cleaned_data['password']            try:                user = models.User.objects.get(name=username)                if user.password == password:                    return redirect('/index/')                else:                    message = "密碼不正確!"            except:                message = "用戶不存在!"        return render(request, 'login/login.html', locals())    login_form = UserForm()    return render(request, 'login/login.html', locals())

說(shuō)明:

  • 對(duì)于非POST方法發(fā)送數(shù)據(jù)時(shí),比如GET方法請(qǐng)求頁(yè)面,返回空的表單,讓用戶可以填入數(shù)據(jù);
  • 對(duì)于POST方法,接收表單數(shù)據(jù),并驗(yàn)證;
  • 使用表單類自帶的is_valid()方法一步完成數(shù)據(jù)驗(yàn)證工作;
  • 驗(yàn)證成功后可以從表單對(duì)象的cleaned_data數(shù)據(jù)字典中獲取表單的具體值;
  • 如果驗(yàn)證不通過(guò),則返回一個(gè)包含先前數(shù)據(jù)的表單給前端頁(yè)面,方便用戶修改。也就是說(shuō),它會(huì)幫你保留先前填寫(xiě)的數(shù)據(jù)內(nèi)容,而不是返回一個(gè)空表!

另外,這里使用了一個(gè)小技巧,Python內(nèi)置了一個(gè)locals()函數(shù),它返回當(dāng)前所有的本地變量字典,我們可以偷懶的將這作為render函數(shù)的數(shù)據(jù)字典參數(shù)值,就不用費(fèi)勁去構(gòu)造一個(gè)形如{'message':message, 'login_form':login_form}的字典了。這樣做的好處當(dāng)然是大大方便了我們,但是同時(shí)也可能往模板傳入了一些多余的變量數(shù)據(jù),造成數(shù)據(jù)冗余降低效率。

7.3.修改login界面

Django的表單很重要的一個(gè)功能就是自動(dòng)生成HTML的form表單內(nèi)容?,F(xiàn)在,我們需要修改一下原來(lái)的login.html文件:

{% extends 'base.html' %}{% load staticfiles %}{% block title %}登錄{% endblock %}{% block css %}<link href="{% static 'css/login.css' %}" rel="stylesheet"/>{% endblock %}{% block content %}    <div class="container">        <div class="col-md-4 col-md-offset-4">          <form class='form-login' action="/login/" method="post">              {% if message %}                  <div class="alert alert-warning">{{ message }}</div>              {% endif %}              {% csrf_token %}              <h2 class="text-center">歡迎登錄</h2>              {{ login_form }}              <button type="reset" class="btn btn-default pull-left">重置</button>              <button type="submit" class="btn btn-primary pull-right">提交</button>          </form>        </div>    </div> <!-- /container -->{% endblock %}

瀏覽器生成的HTML源碼

重新啟動(dòng)服務(wù)器,刷新頁(yè)面,如下圖所示:

<form class='form-login' action="/login/" method="post">    <div class="alert alert-warning">密碼不正確!</div>    <input type='hidden' name='csrfmiddlewaretoken' value='t7MdqJzR7fbiDth5ZQSBpHb22F8sUkjTy32MlEuhXdW8EZPTwcTNuF0PPOHlxKPz' />    <h2 class="text-center">歡迎登錄</h2>    <tr><th><label for="id_username">用戶名:</label></th><td><input type="text" name="username" value="jack" maxlength="128" required id="id_username" /></td></tr>    <tr><th><label for="id_password">密碼:</label></th><td><input type="password" name="password" maxlength="256" required id="id_password" /></td></tr>      <button type="reset" class="btn btn-default pull-left">重置</button>      <button type="submit" class="btn btn-primary pull-right">提交</button></form>

7.4.手動(dòng)渲染表單

直接{{ login_form }}雖然好,啥都不用操心,但是界面真的很丑,往往并不是你想要的,如果你要使用CSS和JS,比如你要引入Bootstarps框架,這些都需要對(duì)表單內(nèi)的input元素進(jìn)行額外控制,那怎么辦呢?手動(dòng)渲染字段就可以了。

可以通過(guò){{ login_form.name_of_field }}獲取每一個(gè)字段,然后分別渲染,如下例所示:

<div class="form-group">  {{ login_form.username.label_tag }}  {{ login_form.username}}</div><div class="form-group">  {{ login_form.password.label_tag }}  {{ login_form.password }}</div>

然后,在form類里添加attr屬性即可,如下所示修改login/forms.py

from django import formsclass UserForm(forms.Form):    username = forms.CharField(label="用戶名", max_length=128, widget=forms.TextInput(attrs={'class': 'form-control'}))    password = forms.CharField(label="密碼", max_length=256, widget=forms.PasswordInput(attrs={'class': 'form-control'}))

再次刷新頁(yè)面,就顯示正常了!

8.圖片驗(yàn)證碼

為了防止機(jī)器人頻繁登錄網(wǎng)站或者破壞分子惡意登錄,很多用戶登錄和注冊(cè)系統(tǒng)都提供了圖形驗(yàn)證碼功能。

驗(yàn)證碼(CAPTCHA)是“Completely Automated Public Turing test to tell Computers and Humans Apart”(全自動(dòng)區(qū)分計(jì)算機(jī)和人類的圖靈測(cè)試)的縮寫(xiě),是一種區(qū)分用戶是計(jì)算機(jī)還是人的公共全自動(dòng)程序??梢苑乐箰阂馄平饷艽a、刷票、論壇灌水,有效防止某個(gè)黑客對(duì)某一個(gè)特定注冊(cè)用戶用特定程序暴力破解方式進(jìn)行不斷的登陸嘗試。

圖形驗(yàn)證碼的歷史比較悠久,到現(xiàn)在已經(jīng)有點(diǎn)英雄末路的味道了。因?yàn)闄C(jī)器學(xué)習(xí)、圖像識(shí)別的存在,機(jī)器人已經(jīng)可以比較正確的識(shí)別圖像內(nèi)的字符了。但不管怎么說(shuō),作為一種防御手段,至少還是可以抵擋一些低級(jí)入門的攻擊手段,抬高了攻擊者的門檻。

在Django中實(shí)現(xiàn)圖片驗(yàn)證碼功能非常簡(jiǎn)單,有現(xiàn)成的第三方庫(kù)可以使用,我們不必自己開(kāi)發(fā)(也要能開(kāi)發(fā)得出來(lái),囧)。這個(gè)庫(kù)叫做django-simple-captcha。

8.1.安裝captcha

直接安裝:pip install django-simple-captcha

Django自動(dòng)幫我們安裝了相關(guān)的依賴庫(kù)sixolefilePillow,其中的Pillow是大名鼎鼎的繪圖模塊。

注冊(cè)captcha

在settings中,將‘captcha’注冊(cè)到app列表里:

INSTALLED_APPS = [    'django.contrib.admin',    'django.contrib.auth',    'django.contrib.contenttypes',    'django.contrib.sessions',    'django.contrib.messages',    'django.contrib.staticfiles',    'login',    'captcha',]

captcha需要在數(shù)據(jù)庫(kù)中建立自己的數(shù)據(jù)表,所以需要執(zhí)行migrate命令生成數(shù)據(jù)表:

python manage.py makemigrationspython manage.py migrate

8.2.添加url路由

根目錄下的urls.py文件中增加captcha對(duì)應(yīng)的網(wǎng)址:

from django.conf.urls import urlfrom django.conf.urls import includefrom django.contrib import adminfrom login import viewsurlpatterns = [    url(r'^admin/', admin.site.urls),    url(r'^index/', views.index),    url(r'^login/', views.login),    url(r'^register/', views.register),    url(r'^logout/', views.logout),    url(r'^captcha', include('captcha.urls'))  # 增加這一行]

8.3.修改forms.py

如果上面都OK了,就可以直接在我們的forms.py文件中添加CaptchaField了。

from django import formsfrom captcha.fields import CaptchaFieldclass UserForm(forms.Form):    username = forms.CharField(label="用戶名", max_length=128, widget=forms.TextInput(attrs={'class': 'form-control'}))    password = forms.CharField(label="密碼", max_length=256, widget=forms.PasswordInput(attrs={'class': 'form-control'}))    captcha = CaptchaField(label='驗(yàn)證碼')

需要提前導(dǎo)入from captcha.fields import CaptchaField,然后就像寫(xiě)普通的form字段一樣添加一個(gè)captcha字段就可以了!

8.4.修改login.html

由于我們前面是手動(dòng)生成的form表單,所以還要修改一下,添加captcha的相關(guān)內(nèi)容,如下所示:

{% extends 'login/base.html' %}{% load staticfiles %}{% block title %}登錄{% endblock %}{% block css %}    <link rel="stylesheet" href="{% static 'css/login.css' %}">{% endblock %}{% block content %}    <div class="container">        <div class="col-md-4 col-md-offset-4">          <form class='form-login' action="/login/" method="post">              {% if message %}                  <div class="alert alert-warning">{{ message }}</div>              {% endif %}              {% csrf_token %}              <h2 class="text-center">歡迎登錄</h2>              <div class="form-group">                  {{ login_form.username.label_tag }}                  {{ login_form.username}}              </div>              <div class="form-group">                  {{ login_form.password.label_tag }}                  {{ login_form.password }}              </div>              <div class="form-group">                  {{ login_form.captcha.errors }}                  {{ login_form.captcha.label_tag }}                  {{ login_form.captcha }}              </div>              <button type="reset" class="btn btn-default pull-left">重置</button>              <button type="submit" class="btn btn-primary pull-right">提交</button>          </form>        </div>    </div> <!-- /container -->{% endblock %}

這里額外增加了一條{{ login_form.captcha.errors }}用于明確指示用戶,你的驗(yàn)證碼不正確

查看效果:

其中驗(yàn)證圖形碼是否正確的工作都是在后臺(tái)自動(dòng)完成的,只需要使用is_valid()這個(gè)forms內(nèi)置的驗(yàn)證方法就一起進(jìn)行了,完全不需要在視圖函數(shù)中添加任何的驗(yàn)證代碼,非常方便快捷!

9.session會(huì)話

因?yàn)橐蛱鼐W(wǎng)HTTP協(xié)議的特性,每一次來(lái)自于用戶瀏覽器的請(qǐng)求(request)都是無(wú)狀態(tài)的、獨(dú)立的。通俗地說(shuō),就是無(wú)法保存用戶狀態(tài),后臺(tái)服務(wù)器根本就不知道當(dāng)前請(qǐng)求和以前及以后請(qǐng)求是否來(lái)自同一用戶。對(duì)于靜態(tài)網(wǎng)站,這可能不是個(gè)問(wèn)題,而對(duì)于動(dòng)態(tài)網(wǎng)站,尤其是京東、天貓、銀行等購(gòu)物或金融網(wǎng)站,無(wú)法識(shí)別用戶并保持用戶狀態(tài)是致命的,根本就無(wú)法提供服務(wù)。你可以嘗試將瀏覽器的cookie功能關(guān)閉,你會(huì)發(fā)現(xiàn)將無(wú)法在京東登錄和購(gòu)物。

為了實(shí)現(xiàn)連接狀態(tài)的保持功能,網(wǎng)站會(huì)通過(guò)用戶的瀏覽器在用戶機(jī)器內(nèi)被限定的硬盤(pán)位置中寫(xiě)入一些數(shù)據(jù),也就是所謂的Cookie。通過(guò)Cookie可以保存一些諸如用戶名、瀏覽記錄、表單記錄、登錄和注銷等各種數(shù)據(jù)。但是這種方式非常不安全,因?yàn)镃ookie保存在用戶的機(jī)器上,如果Cookie被偽造、篡改或刪除,就會(huì)造成極大的安全威脅,因此,現(xiàn)代網(wǎng)站設(shè)計(jì)通常將Cookie用來(lái)保存一些不重要的內(nèi)容,實(shí)際的用戶數(shù)據(jù)和狀態(tài)還是以Session會(huì)話的方式保存在服務(wù)器端。

Session依賴Cookie!但與Cookie不同的地方在于Session將所有的數(shù)據(jù)都放在服務(wù)器端,用戶瀏覽器的Cookie中只會(huì)保存一個(gè)非明文的識(shí)別信息,比如哈希值。

Django提供了一個(gè)通用的Session框架,并且可以使用多種session數(shù)據(jù)的保存方式:

  • 保存在數(shù)據(jù)庫(kù)內(nèi)
  • 保存到緩存
  • 保存到文件內(nèi)
  • 保存到cookie內(nèi)

通常情況,沒(méi)有特別需求的話,請(qǐng)使用保存在數(shù)據(jù)庫(kù)內(nèi)的方式,盡量不要保存到Cookie內(nèi)。

Django的session框架默認(rèn)啟用,并已經(jīng)注冊(cè)在app設(shè)置內(nèi),如果真的沒(méi)有啟用,那么參考下面的內(nèi)容添加有說(shuō)明的那兩行,再執(zhí)行migrate命令創(chuàng)建數(shù)據(jù)表,就可以使用session了。

# Application definitionINSTALLED_APPS = [    'django.contrib.admin',    'django.contrib.auth',    'django.contrib.contenttypes',    'django.contrib.sessions',    # 這一行    'django.contrib.messages',    'django.contrib.staticfiles',]MIDDLEWARE = [    'django.middleware.security.SecurityMiddleware',    'django.contrib.sessions.middleware.SessionMiddleware',  # 這一行    'django.middleware.common.CommonMiddleware',    'django.middleware.csrf.CsrfViewMiddleware',    'django.contrib.auth.middleware.AuthenticationMiddleware',    'django.contrib.messages.middleware.MessageMiddleware',    'django.middleware.clickjacking.XFrameOptionsMiddleware',]

當(dāng)session啟用后,傳遞給視圖request參數(shù)的HttpRequest對(duì)象將包含一個(gè)session屬性,就像一個(gè)字典對(duì)象一樣。你可以在Django的任何地方讀寫(xiě)request.session屬性,或者多次編輯使用它。

下面是session使用參考:

class backends.base.SessionBase        # 這是所有會(huì)話對(duì)象的基類,包含標(biāo)準(zhǔn)的字典方法:        __getitem__(key)            Example: fav_color = request.session['fav_color']        __setitem__(key, value)            Example: request.session['fav_color'] = 'blue'        __delitem__(key)            Example: del request.session['fav_color']  # 如果不存在會(huì)拋出異常        __contains__(key)            Example: 'fav_color' in request.session        get(key, default=None)            Example: fav_color = request.session.get('fav_color', 'red')        pop(key, default=__not_given)            Example: fav_color = request.session.pop('fav_color', 'blue')
# 類似字典數(shù)據(jù)類型的內(nèi)置方法        keys()        items()        setdefault()        clear()        # 它還有下面的方法:        flush()            # 刪除當(dāng)前的會(huì)話數(shù)據(jù)和會(huì)話cookie。經(jīng)常用在用戶退出后,刪除會(huì)話。        set_test_cookie()            # 設(shè)置一個(gè)測(cè)試cookie,用于探測(cè)用戶瀏覽器是否支持cookies。由于cookie的工作機(jī)制,你只有在下次用戶請(qǐng)求的時(shí)候才可以測(cè)試。        test_cookie_worked()            # 返回True或者False,取決于用戶的瀏覽器是否接受測(cè)試cookie。你必須在之前先調(diào)用set_test_cookie()方法。        delete_test_cookie()            # 刪除測(cè)試cookie。        set_expiry(value)            # 設(shè)置cookie的有效期??梢詡鬟f不同類型的參數(shù)值:        · 如果值是一個(gè)整數(shù),session將在對(duì)應(yīng)的秒數(shù)后失效。例如request.session.set_expiry(300) 將在300秒后失效.        · 如果值是一個(gè)datetime或者timedelta對(duì)象, 會(huì)話將在指定的日期失效        · 如果為0,在用戶關(guān)閉瀏覽器后失效        · 如果為None,則將使用全局會(huì)話失效策略        失效時(shí)間從上一次會(huì)話被修改的時(shí)刻開(kāi)始計(jì)時(shí)。        get_expiry_age()            # 返回多少秒后失效的秒數(shù)。對(duì)于沒(méi)有自定義失效時(shí)間的會(huì)話,這等同于SESSION_COOKIE_AGE.            # 這個(gè)方法接受2個(gè)可選的關(guān)鍵字參數(shù)        · modification:會(huì)話的最后修改時(shí)間(datetime對(duì)象)。默認(rèn)是當(dāng)前時(shí)間。        ·expiry: 會(huì)話失效信息,可以是datetime對(duì)象,也可以是int或None        get_expiry_date()            # 和上面的方法類似,只是返回的是日期        get_expire_at_browser_close()            # 返回True或False,根據(jù)用戶會(huì)話是否是瀏覽器關(guān)閉后就結(jié)束。        clear_expired()            # 刪除已經(jīng)失效的會(huì)話數(shù)據(jù)。        cycle_key()            # 創(chuàng)建一個(gè)新的會(huì)話秘鑰用于保持當(dāng)前的會(huì)話數(shù)據(jù)。django.contrib.auth.login() 會(huì)調(diào)用這個(gè)方法。

9.1.使用session

首先,修改login/views.py中的login()視圖函數(shù):

def login(request):    if request.session.get('is_login',None):        return redirect('/index')    if request.method == "POST":        login_form = UserForm(request.POST)        message = "請(qǐng)檢查填寫(xiě)的內(nèi)容!"        if login_form.is_valid():            username = login_form.cleaned_data['username']            password = login_form.cleaned_data['password']            try:                user = models.User.objects.get(name=username)                if user.password == password:                    request.session['is_login'] = True                    request.session['user_id'] = user.id                    request.session['user_name'] = user.name                    return redirect('/index/')                else:                    message = "密碼不正確!"            except:                message = "用戶不存在!"        return render(request, 'login/login.html', locals())    login_form = UserForm()    return render(request, 'login/login.html', locals())

通過(guò)下面的if語(yǔ)句,我們不允許重復(fù)登錄:

if request.session.get('is_login',None):    return redirect("/index/")

通過(guò)下面的語(yǔ)句,我們往session字典內(nèi)寫(xiě)入用戶狀態(tài)和數(shù)據(jù):

request.session['is_login'] = Truerequest.session['user_id'] = user.idrequest.session['user_name'] = user.name

你完全可以往里面寫(xiě)任何數(shù)據(jù),不僅僅限于用戶相關(guān)!

既然有了session記錄用戶登錄狀態(tài),那么就可以完善我們的登出視圖函數(shù)了:

def logout(request):    if not request.session.get('is_login', None):        # 如果本來(lái)就未登錄,也就沒(méi)有登出一說(shuō)        return redirect("/index/")    request.session.flush()    # 或者使用下面的方法    # del request.session['is_login']    # del request.session['user_id']    # del request.session['user_name']    return redirect("/index/")

flush()方法是比較安全的一種做法,而且一次性將session中的所有內(nèi)容全部清空,確保不留后患。但也有不好的地方,那就是如果你在session中夾帶了一點(diǎn)‘私貨’,會(huì)被一并刪除,這一點(diǎn)一定要注意。

9.2.完善頁(yè)面

有了用戶狀態(tài),就可以根據(jù)用戶登錄與否,展示不同的頁(yè)面,比如導(dǎo)航條內(nèi)容:

首先,修改base.html文件:

<div class="collapse navbar-collapse" id="my-nav">          <ul class="nav navbar-nav">            <li class="active"><a href="/index/">主頁(yè)</a></li>          </ul>          <ul class="nav navbar-nav navbar-right">              {% if request.session.is_login %}                  <li><a href="#">當(dāng)前在線:{{ request.session.user_name }}</a></li>                  <li><a href="/logout/">登出</a></li>              {% else %}                  <li><a href="/login/">登錄</a></li>                  <li><a href="/register/">注冊(cè)</a></li>              {% endif %}          </ul>        </div><!-- /.navbar-collapse -->      </div><!-- /.container-fluid -->

通過(guò)if判斷,當(dāng)?shù)卿洉r(shí),顯示當(dāng)前用戶名和登出按鈕。未登錄時(shí),顯示登錄和注冊(cè)按鈕。

注意其中的模板語(yǔ)言,{{ request }}這個(gè)變量會(huì)被默認(rèn)傳入模板中,可以通過(guò)圓點(diǎn)的調(diào)用方式,獲取它內(nèi)部的{{ request.session }},再進(jìn)一步的獲取session中的內(nèi)容。其實(shí){{ request }}中的數(shù)據(jù)遠(yuǎn)不止此,例如{{ request.path }}就可以獲取先前的url地址。

再修改一下index.html頁(yè)面,根據(jù)登錄與否的不同,顯示不同的內(nèi)容:

{% extends 'base.html' %}{% block title %}主頁(yè){% endblock %}{% block content %}    {% if request.session.is_login %}    <h1>你好,{{ request.session.user_name }}!歡迎回來(lái)!</h1>    {% else %}    <h1>你尚未登錄,只能訪問(wèn)公開(kāi)內(nèi)容!</h1>    {% endif %}{% endblock %}

看下效果:

10.注冊(cè)視圖

10.1.創(chuàng)建forms

/login/forms.py中添加一個(gè)新的表單類:

class RegisterForm(forms.Form):    gender = (        ('male', "男"),        ('female', "女"),    )    username = forms.CharField(label="用戶名", max_length=128, widget=forms.TextInput(attrs={'class': 'form-control'}))    password1 = forms.CharField(label="密碼", max_length=256, widget=forms.PasswordInput(attrs={'class': 'form-control'}))    password2 = forms.CharField(label="確認(rèn)密碼", max_length=256, widget=forms.PasswordInput(attrs={'class': 'form-control'}))    email = forms.EmailField(label="郵箱地址", widget=forms.EmailInput(attrs={'class': 'form-control'}))    sex = forms.ChoiceField(label='性別', choices=gender)    captcha = CaptchaField(label='驗(yàn)證碼')

說(shuō)明:

  • gender和User模型中的一樣,其實(shí)可以拉出來(lái)作為常量共用,為了直觀,特意重寫(xiě)一遍;
  • password1和password2,用于輸入兩遍密碼,并進(jìn)行比較,防止誤輸密碼;
  • email是一個(gè)郵箱輸入框;
  • sex是一個(gè)select下拉框;

10.2.完善register.html

同樣地,類似login.html文件,我們?cè)趓egister.html中編寫(xiě)forms相關(guān)條目:

{% extends 'login/base.html' %}{% block title %}注冊(cè){% endblock %}{% block content %}    <div class="container">        <div class="col-md-4 col-md-offset-4">          <form class='form-register' action="/register/" method="post">              {% if message %}                  <div class="alert alert-warning">{{ message }}</div>              {% endif %}              {% csrf_token %}              <h2 class="text-center">歡迎注冊(cè)</h2>              <div class="form-group">                  {{ register_form.username.label_tag }}                  {{ register_form.username}}              </div>              <div class="form-group">                  {{ register_form.password1.label_tag }}                  {{ register_form.password1 }}              </div>              <div class="form-group">                  {{ register_form.password2.label_tag }}                  {{ register_form.password2 }}              </div>              <div class="form-group">                  {{ register_form.email.label_tag }}                  {{ register_form.email }}              </div>              <div class="form-group">                  {{ register_form.sex.label_tag }}                  {{ register_form.sex }}              </div>              <div class="form-group">                  {{ register_form.captcha.errors }}                  {{ register_form.captcha.label_tag }}                  {{ register_form.captcha }}              </div>              <button type="reset" class="btn btn-default pull-left">重置</button>              <button type="submit" class="btn btn-primary pull-right">提交</button>          </form>        </div>    </div> <!-- /container -->{% endblock %}

10.3.注冊(cè)視圖

進(jìn)入/login/views.py文件,現(xiàn)在來(lái)完善我們的register()視圖:

def register(request):    if request.session.get('is_login', None):        # 登錄狀態(tài)不允許注冊(cè)。你可以修改這條原則!        return redirect("/index/")    if request.method == "POST":        register_form = RegisterForm(request.POST)        message = "請(qǐng)檢查填寫(xiě)的內(nèi)容!"        if register_form.is_valid():  # 獲取數(shù)據(jù)            username = register_form.cleaned_data['username']            password1 = register_form.cleaned_data['password1']            password2 = register_form.cleaned_data['password2']            email = register_form.cleaned_data['email']            sex = register_form.cleaned_data['sex']            if password1 != password2:  # 判斷兩次密碼是否相同                message = "兩次輸入的密碼不同!"                return render(request, 'login/register.html', locals())            else:                same_name_user = models.User.objects.filter(name=username)                if same_name_user:  # 用戶名唯一                    message = '用戶已經(jīng)存在,請(qǐng)重新選擇用戶名!'                    return render(request, 'login/register.html', locals())                same_email_user = models.User.objects.filter(email=email)                if same_email_user:  # 郵箱地址唯一                    message = '該郵箱地址已被注冊(cè),請(qǐng)使用別的郵箱!'                    return render(request, 'login/register.html', locals())                # 當(dāng)一切都OK的情況下,創(chuàng)建新用戶                new_user = models.User.objects.create()                new_user.name = username                new_user.password = password1                new_user.email = email                new_user.sex = sex                new_user.save()                return redirect('/login/')  # 自動(dòng)跳轉(zhuǎn)到登錄頁(yè)面    register_form = RegisterForm()    return render(request, 'login/register.html', locals())

從大體邏輯上,也是先實(shí)例化一個(gè)RegisterForm的對(duì)象,然后使用is_valide()驗(yàn)證數(shù)據(jù),再?gòu)?code>cleaned_data中獲取數(shù)據(jù)。

重點(diǎn)在于注冊(cè)邏輯,首先兩次輸入的密碼必須相同,其次不能存在相同用戶名和郵箱,最后如果條件都滿足,利用ORM的API,創(chuàng)建一個(gè)用戶實(shí)例,然后保存到數(shù)據(jù)庫(kù)內(nèi)。

看一下注冊(cè)的頁(yè)面:

注冊(cè)成功在admin后臺(tái)可以看到注冊(cè)的用戶

10.4.密碼加密

用戶注冊(cè)的密碼應(yīng)該加密才對(duì)

對(duì)于如何加密密碼,有很多不同的途徑,其安全程度也高低不等。這里我們使用Python內(nèi)置的hashlib庫(kù),使用哈希值的方式加密密碼,可能安全等級(jí)不夠高,但足夠簡(jiǎn)單,方便使用,不是么?

首先在login/views.py中編寫(xiě)一個(gè)hash函數(shù):

import hashlibdef hash_code(s, salt='mysite'):# 加點(diǎn)鹽  h = hashlib.sha256()  s += salt  h.update(s.encode()) # update方法只接收bytes類型  return h.hexdigest()

然后,我們還要對(duì)login()和register()視圖進(jìn)行一下修改:

#loginif user.password == hash_code(password):  # 哈希值和數(shù)據(jù)庫(kù)內(nèi)的值進(jìn)行比對(duì)#registernew_user.password = hash_code(password1)  # 使用加密密碼

views.py全部代碼

# login/views.pyfrom django.shortcuts import render,redirectfrom . import modelsfrom .forms import UserForm,RegisterFormimport hashlibdef index(request):    pass    return render(request,'login/index.html')def login(request):    if request.session.get('is_login', None):        return redirect("/index/")    if request.method == "POST":        login_form = UserForm(request.POST)        message = "請(qǐng)檢查填寫(xiě)的內(nèi)容!"        if login_form.is_valid():            username = login_form.cleaned_data['username']            password = login_form.cleaned_data['password']            try:                user = models.User.objects.get(name=username)                if user.password == hash_code(password):  # 哈希值和數(shù)據(jù)庫(kù)內(nèi)的值進(jìn)行比對(duì)                    request.session['is_login'] = True                    request.session['user_id'] = user.id                    request.session['user_name'] = user.name                    return redirect('/index/')                else:                    message = "密碼不正確!"            except:                message = "用戶不存在!"        return render(request, 'login/login.html', locals())    login_form = UserForm()    return render(request, 'login/login.html', locals())def register(request):    if request.session.get('is_login', None):        # 登錄狀態(tài)不允許注冊(cè)。你可以修改這條原則!        return redirect("/index/")    if request.method == "POST":        register_form = RegisterForm(request.POST)        message = "請(qǐng)檢查填寫(xiě)的內(nèi)容!"        if register_form.is_valid():  # 獲取數(shù)據(jù)            username = register_form.cleaned_data['username']            password1 = register_form.cleaned_data['password1']            password2 = register_form.cleaned_data['password2']            email = register_form.cleaned_data['email']            sex = register_form.cleaned_data['sex']            if password1 != password2:  # 判斷兩次密碼是否相同                message = "兩次輸入的密碼不同!"                return render(request, 'login/register.html', locals())            else:                same_name_user = models.User.objects.filter(name=username)                if same_name_user:  # 用戶名唯一                    message = '用戶已經(jīng)存在,請(qǐng)重新選擇用戶名!'                    return render(request, 'login/register.html', locals())                same_email_user = models.User.objects.filter(email=email)                if same_email_user:  # 郵箱地址唯一                    message = '該郵箱地址已被注冊(cè),請(qǐng)使用別的郵箱!'                    return render(request, 'login/register.html', locals())                # 當(dāng)一切都OK的情況下,創(chuàng)建新用戶                new_user = models.User.objects.create()                new_user.name = username                new_user.password = hash_code(password1)  # 使用加密密碼                new_user.email = email                new_user.sex = sex                new_user.save()                return redirect('/login/')  # 自動(dòng)跳轉(zhuǎn)到登錄頁(yè)面    register_form = RegisterForm()    return render(request, 'login/register.html', locals())def logout(request):    if not request.session.get('is_login',None):        return redirect('/index/')    request.session.flush()    return redirect('/index/')def hash_code(s, salt='mysite_login'):    h = hashlib.sha256()    s += salt    h.update(s.encode())  # update方法只接收bytes類型    return h.hexdigest()views.py全部代碼

重啟服務(wù)器,進(jìn)入注冊(cè)頁(yè)面,新建一個(gè)用戶,然后進(jìn)入admin后臺(tái),查看用戶的密碼情況:

再使用該用戶登錄一下,大功告成!

可以看到密碼長(zhǎng)度根據(jù)你哈希算法的不同,已經(jīng)變得很長(zhǎng)了,所以前面model中設(shè)置password字段時(shí),不要想當(dāng)然的將max_length設(shè)置為16這么小的數(shù)字。

11.使用Django發(fā)送郵件

通常而言,我們?cè)谟脩糇?cè)成功,實(shí)際登陸之前,會(huì)發(fā)送一封電子郵件到對(duì)方的注冊(cè)郵箱中,表示歡迎。進(jìn)一步的還可能要求用戶點(diǎn)擊郵件中的鏈接,進(jìn)行注冊(cè)確認(rèn)。

下面就讓我們先看看如何在Django中發(fā)送郵件吧。

11.1.在Django中發(fā)送郵件

其實(shí)在Python中已經(jīng)內(nèi)置了一個(gè)smtp郵件發(fā)送模塊,Django在此基礎(chǔ)上進(jìn)行了簡(jiǎn)單地封裝。

首先,我們需要在項(xiàng)目的settings文件中配置郵件發(fā)送參數(shù),分別如下:

EMAIL_USE_SSL = TrueEMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'EMAIL_HOST = 'smtp.qq.com'EMAIL_PORT = 465EMAIL_HOST_USER = 'xxxxxxxx@qq.com'EMAIL_HOST_PASSWORD = 'x x x x x x x'DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
  • 第一行指定SSL加密,qq郵箱有此要求,第二行發(fā)送郵件的后端模塊,大多數(shù)情況下照抄!
  • 第三行,不用說(shuō),發(fā)送方的smtp服務(wù)器地址,建議使用新浪家的;
  • 第四行,smtp服務(wù)端口,qq郵箱默認(rèn)為465;
  • 第五行,你在發(fā)送服務(wù)器的用戶名;
  • 第六行,對(duì)應(yīng)用戶的密碼。

特別說(shuō)明:

  • 郵箱要支持smtp服務(wù),且打開(kāi),某些郵件公司可能不開(kāi)放smtp服務(wù)
  • 某些公司要求使用ssl安全機(jī)制
  • 某些smtp服務(wù)對(duì)主機(jī)名格式有要求

配置好了參數(shù),就可以先測(cè)試一下郵件功能了。

在項(xiàng)目根目錄下新建一個(gè)send_mail.py文件,然后寫(xiě)入下面的內(nèi)容:

import osfrom django.core.mail import send_mailos.environ['DJANGO_SETTINGS_MODULE'] = 'projectname.settings'if __name__ == '__main__':       send_mail(        '來(lái)自www.xxxxx.com的測(cè)試郵件',        '歡迎訪問(wèn)www.xxxxx.com,這里是xx站點(diǎn),本站專注于xx內(nèi)容的分享!',        'xxx@qq.com',        ['xxx@qq.com'],    )#HTML格式郵件import osfrom django.core.mail import EmailMultiAlternativesos.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'if __name__ == '__main__':    subject, from_email, to = '來(lái)自www.xxxxx.com的測(cè)試郵件', 'xxx@qq.com', 'xxx@qq.com'    text_content = '歡迎訪問(wèn)www.xxxxx.com,這里是xx站點(diǎn),專注于xx技術(shù)的分享!'    html_content = '<p>歡迎訪問(wèn)<a  target=blank>www.xxx.com</a>,這里是xx的站點(diǎn),專注于xx技術(shù)的分享!</p>'    msg = EmailMultiAlternatives(subject, text_content, from_email, [to])    msg.attach_alternative(html_content, "text/html")    msg.send()

對(duì)于send_mail方法,第一個(gè)參數(shù)是郵件主題subject;第二個(gè)參數(shù)是郵件具體內(nèi)容;第三個(gè)參數(shù)是郵件發(fā)送方,需要和你settings中的一致;第四個(gè)參數(shù)是接受方的郵件地址列表。請(qǐng)按你自己實(shí)際情況修改發(fā)送方和接收方的郵箱地址。

運(yùn)行send_mail.py文件,注意不是運(yùn)行Django服務(wù)器。然后到你的目的地郵箱查看郵件是否收到。

12. 郵件注冊(cè)確認(rèn)

12.1.創(chuàng)建模型

既然要區(qū)分通過(guò)和未通過(guò)郵件確認(rèn)的用戶,那么必須給用戶添加一個(gè)是否進(jìn)行過(guò)郵件確認(rèn)的屬性。

另外,我們要?jiǎng)?chuàng)建一張新表,用于保存用戶的確認(rèn)碼以及注冊(cè)提交的時(shí)間。

全新、完整的/login/models.py文件如下:

from django.db import models# Create your models here.class User(models.Model):    gender = (        ('male', "男"),        ('female', "女"),    )    name = models.CharField(max_length=128, unique=True)    password = models.CharField(max_length=256)    email = models.EmailField(unique=True)    sex = models.CharField(max_length=32, choices=gender, default="男")    c_time = models.DateTimeField(auto_now_add=True)    has_confirmed = models.BooleanField(default=False)    def __str__(self):        return self.name    class Meta:        ordering = ["-c_time"]        verbose_name = "用戶"        verbose_name_plural = "用戶"class ConfirmString(models.Model):    code = models.CharField(max_length=256)    user = models.OneToOneField('User')    c_time = models.DateTimeField(auto_now_add=True)    def __str__(self):        return self.user.name + ":   " + self.code    class Meta:        ordering = ["-c_time"]        verbose_name = "確認(rèn)碼"        verbose_name_plural = "確認(rèn)碼"

說(shuō)明:

  • User模型新增了has_confirmed字段,這是個(gè)布爾值,默認(rèn)為False,也就是未進(jìn)行郵件注冊(cè);
  • ConfirmString模型保存了用戶和注冊(cè)碼之間的關(guān)系,一對(duì)一的形式;
  • code字段是哈希后的注冊(cè)碼;
  • user是關(guān)聯(lián)的一對(duì)一用戶;
  • c_time是注冊(cè)的提交時(shí)間,"-c_time"表示降序排列.

這里有個(gè)問(wèn)題可以討論一下:是否需要?jiǎng)?chuàng)建ConfirmString新表,可否都放在User表里?我認(rèn)為如果全都放在User中,不利于管理,查詢速度慢,創(chuàng)建新表有利于區(qū)分已確認(rèn)和未確認(rèn)的用戶。最終的選擇可以根據(jù)你的實(shí)際情況具體分析。

模型修改和創(chuàng)建完畢,需要執(zhí)行migrate命令,一定不要忘了。

順便修改一下admin.py文件,方便我們?cè)诤笈_(tái)修改和觀察數(shù)據(jù)。

# login/admin.pyfrom django.contrib import admin# Register your models here.from . import modelsadmin.site.register(models.User)admin.site.register(models.ConfirmString)

12.2.修改視圖

首先,要修改我們的register()視圖的邏輯:

def register(request):    if request.session.get('is_login', None):        # 登錄狀態(tài)不允許注冊(cè)。你可以修改這條原則!        return redirect("/index/")    if request.method == "POST":        register_form = forms.RegisterForm(request.POST)        message = "請(qǐng)檢查填寫(xiě)的內(nèi)容!"        if register_form.is_valid():  # 獲取數(shù)據(jù)            username = register_form.cleaned_data['username']            password1 = register_form.cleaned_data['password1']            password2 = register_form.cleaned_data['password2']            email = register_form.cleaned_data['email']            sex = register_form.cleaned_data['sex']            if password1 != password2:  # 判斷兩次密碼是否相同                message = "兩次輸入的密碼不同!"                return render(request, 'login/register.html', locals())            else:                same_name_user = models.User.objects.filter(name=username)                if same_name_user:  # 用戶名唯一                    message = '用戶已經(jīng)存在,請(qǐng)重新選擇用戶名!'                    return render(request, 'login/register.html', locals())                same_email_user = models.User.objects.filter(email=email)                if same_email_user:  # 郵箱地址唯一                    message = '該郵箱地址已被注冊(cè),請(qǐng)使用別的郵箱!'                    return render(request, 'login/register.html', locals())                # 當(dāng)一切都OK的情況下,創(chuàng)建新用戶                new_user = models.User()                new_user.name = username                new_user.password = hash_code(password1)  # 使用加密密碼                new_user.email = email                new_user.sex = sex                new_user.save()                code = make_confirm_string(new_user)                send_email(email, code)                message = '請(qǐng)前往注冊(cè)郵箱,進(jìn)行郵件確認(rèn)!'                return render(request, 'login/confirm.html', locals())  # 跳轉(zhuǎn)到等待郵件確認(rèn)頁(yè)面。    register_form = forms.RegisterForm()    return render(request, 'login/register.html', locals())

make_confirm_string()是創(chuàng)建確認(rèn)碼對(duì)象的方法,代碼如下:

def make_confirm_string(user):    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")    code = hash_code(user.name, now)    models.ConfirmString.objects.create(code=code, user=user,)    return code

在文件頂部要先導(dǎo)入datetime模塊。

make_confirm_string()方法接收一個(gè)用戶對(duì)象作為參數(shù)。首先利用datetime模塊生成一個(gè)當(dāng)前時(shí)間的字符串now,再調(diào)用我們前面編寫(xiě)的hash_code()方法以用戶名為基礎(chǔ),now為‘鹽’,生成一個(gè)獨(dú)一無(wú)二的哈希值,再調(diào)用ConfirmString模型的create()方法,生成并保存一個(gè)確認(rèn)碼對(duì)象。最后返回這個(gè)哈希值。

send_email(email, code)方法接收兩個(gè)參數(shù),分別是注冊(cè)的郵箱和前面生成的哈希值,代碼如下:

def send_email(email, code):    from django.core.mail import EmailMultiAlternatives    subject = '來(lái)自www.xxxxx.com的測(cè)試郵件'    text_content = '歡迎訪問(wèn)www.xxxxx.com,這里是xx站點(diǎn),專注于xx技術(shù)的分享!'    html_content = '<p>歡迎注冊(cè)<a href="http://{}/confirm/?code={}" target="blank>www.xxx.com</a>,這里是xx的站點(diǎn),專注于xx技術(shù)的分享!</p>'.format('127.0.0.1',code,settings.CONFIRM_DAYS)    msg = EmailMultiAlternatives(subject, text_content, from_email, [to])    msg.attach_alternative(html_content, "text/html")    msg.send()

首先我們需要導(dǎo)入settings配置文件from django.conf import settings。

郵件內(nèi)容中的所有字符串都可以根據(jù)你的實(shí)際情況進(jìn)行修改。其中關(guān)鍵在于<a href=''>中鏈接地址的格式,我這里使用了硬編碼的'127.0.0.1:8000',請(qǐng)酌情修改,url里的參數(shù)名為code,它保存了關(guān)鍵的注冊(cè)確認(rèn)碼,最后的有效期天數(shù)為設(shè)置在settings中的CONFIRM_DAYS。所有的這些都是可以定制的!

下面是郵件相關(guān)的settings配置:

EMAIL_USE_SSL = TrueEMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'EMAIL_HOST = 'smtp.qq.com'EMAIL_PORT = 465EMAIL_HOST_USER = 'xxxxxxxx@qq.com'EMAIL_HOST_PASSWORD = 'x x x x x x x'DEFAULT_FROM_EMAIL = EMAIL_HOST_USERCONFIRM_DAYS = 7

12.3.處理郵件確認(rèn)請(qǐng)求

首先,在根目錄的urls.py中添加一條url:

url(r'^confirm/$', views.user_confirm),

其次,在login/views.py中添加一個(gè)user_confirm視圖。

def user_confirm(request):    code = request.GET.get('code', None)    message = ''    try:        confirm = models.ConfirmString.objects.get(code=code)    except:        message = '無(wú)效的確認(rèn)請(qǐng)求!'        return render(request, 'login/confirm.html', locals())    c_time = confirm.c_time    now = datetime.datetime.now()    if now > c_time + datetime.timedelta(settings.CONFIRM_DAYS):        confirm.user.delete()        message = '您的郵件已經(jīng)過(guò)期!請(qǐng)重新注冊(cè)!'        return render(request, 'login/confirm.html', locals())    else:        confirm.user.has_confirmed = True        confirm.user.save()        confirm.delete()        message = '感謝確認(rèn),請(qǐng)使用賬戶登錄!'        return render(request, 'login/confirm.html', locals())

說(shuō)明:

  • 通過(guò)request.GET.get('code', None)從請(qǐng)求的url地址中獲取確認(rèn)碼;
  • 先去數(shù)據(jù)庫(kù)內(nèi)查詢是否有對(duì)應(yīng)的確認(rèn)碼;
  • 如果沒(méi)有,返回confirm.html頁(yè)面,并提示;
  • 如果有,獲取注冊(cè)的時(shí)間c_time,加上設(shè)置的過(guò)期天數(shù),這里是7天,然后與現(xiàn)在時(shí)間點(diǎn)進(jìn)行對(duì)比;
  • 如果時(shí)間已經(jīng)超期,刪除注冊(cè)的用戶,同時(shí)注冊(cè)碼也會(huì)一并刪除,然后返回confirm.html頁(yè)面,并提示;
  • 如果未超期,修改用戶的has_confirmed字段為True,并保存,表示通過(guò)確認(rèn)了。然后刪除注冊(cè)碼,但不刪除用戶本身。最后返回confirm.html頁(yè)面,并提示。

這里需要一個(gè)confirm.html頁(yè)面,我們將它創(chuàng)建在/login/templates/login/下面:

{% extends 'base.html' %}{% block title %}注冊(cè)確認(rèn){% endblock %}{% block content %}    <div class="row">        <h1 class="text-center">{{ message }}</h1>    </div>    <script>        window.setTimeout("window.location='/login/'",2000);    </script>{% endblock %}

頁(yè)面中通過(guò)JS代碼,設(shè)置2秒后自動(dòng)跳轉(zhuǎn)到登錄頁(yè)面。

12.4.修改登錄規(guī)則

既然未進(jìn)行郵件確認(rèn)的用戶不能登錄,那么我們就必須修改登錄規(guī)則,如下所示:

def login(request):    if request.session.get('is_login', None):        return redirect("/index/")    if request.method == "POST":        login_form = forms.UserForm(request.POST)        message = "請(qǐng)檢查填寫(xiě)的內(nèi)容!"        if login_form.is_valid():            username = login_form.cleaned_data['username']            password = login_form.cleaned_data['password']            try:                user = models.User.objects.get(name=username)                if not user.has_confirmed:                    message = "該用戶還未通過(guò)郵件確認(rèn)!"                    return render(request, 'login/login.html', locals())                if user.password == hash_code(password):  # 哈希值和數(shù)據(jù)庫(kù)內(nèi)的值進(jìn)行比對(duì)                    request.session['is_login'] = True                    request.session['user_id'] = user.id                    request.session['user_name'] = user.name                    return redirect('/index/')                else:                    message = "密碼不正確!"            except:                message = "用戶不存在!"        return render(request, 'login/login.html', locals())    login_form = forms.UserForm()    return render(request, 'login/login.html', locals())

關(guān)鍵是下面的部分:

if not user.has_confirmed:    message = "該用戶還未通過(guò)郵件確認(rèn)!"    return render(request, 'login/login.html', locals())

最后,貼出view.py的整體代碼,供大家參考:

from django.shortcuts import render,redirectfrom . import modelsfrom . import formsimport hashlibfrom django.conf import settingsdef make_confirm_string(user):    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")    code = hash_code(user.name, now)    models.ConfirmString.objects.create(code=code, user=user,)    return codedef send_email(email,code):    from django.core.mail import EmailMultiAlternatives    subject, from_email, to = '來(lái)自www.robindongblog.com的測(cè)試郵件','1361623944@qq.com','1505955202@qq.com'    text_content = '歡迎注冊(cè)訪問(wèn)www.robindongblog.com,這里是wo的博客和教程站點(diǎn),本站專注于Python和Django技術(shù)的分享!'    html_content = '''                    <p>感謝注冊(cè)<a href="http://{}/confirm/?code={}" target=blank>robin-dong.github.io</a>,                    這里是wo的博客和教程站點(diǎn),專注于Python和Django技術(shù)的分享!</p>                    <p>請(qǐng)點(diǎn)擊站點(diǎn)鏈接完成注冊(cè)確認(rèn)!</p>                    <p>此鏈接有效期為{}天!</p>                    '''.format('127.0.0.1:8000', code, settings.CONFIRM_DAYS)    msg = EmailMultiAlternatives(subject, text_content, from_email, [to])    msg.attach_alternative(html_content, "text/html")    msg.send()    print('done')def hash_code(s, salt='mysite'):# 加點(diǎn)鹽    h = hashlib.sha256()    s += salt    h.update(s.encode()) # update方法只接收bytes類型    return h.hexdigest()# Create your views here.def index(request):    pass    return render(request,'login/index.html')def login(request):    if request.session.get('is_login', None):        return redirect("/index/")    if request.method == "POST":        login_form = forms.UserForm(request.POST)        message = "請(qǐng)檢查填寫(xiě)的內(nèi)容!"        if login_form.is_valid():            username = login_form.cleaned_data['username']            password = login_form.cleaned_data['password']            try:                user = models.User.objects.get(name=username)                if not user.has_confirmed:                    message = "該用戶還未通過(guò)郵件確認(rèn)!"                    return render(request, 'login/login.html', locals())                if user.password == hash_code(password):  # 哈希值和數(shù)據(jù)庫(kù)內(nèi)的值進(jìn)行比對(duì)                    request.session['is_login'] = True                    request.session['user_id'] = user.id                    request.session['user_name'] = user.name                    return redirect('/index/')                else:                    message = "密碼不正確!"            except:                message = "用戶不存在!"        return render(request, 'login/login.html', locals())    login_form = forms.UserForm()    return render(request, 'login/login.html', locals())def register(request):    if request.session.get('is_login', None):        # 登錄狀態(tài)不允許注冊(cè)。你可以修改這條原則!        return redirect("/index/")    if request.method == "POST":        register_form = forms.RegisterForm(request.POST)        message = "請(qǐng)檢查填寫(xiě)的內(nèi)容!"        if register_form.is_valid():  # 獲取數(shù)據(jù)            username = register_form.cleaned_data['username']            password1 = register_form.cleaned_data['password1']            password2 = register_form.cleaned_data['password2']            email = register_form.cleaned_data['email']            sex = register_form.cleaned_data['sex']            if password1 != password2:  # 判斷兩次密碼是否相同                message = "兩次輸入的密碼不同!"                return render(request, 'login/register.html', locals())            else:                same_name_user = models.User.objects.filter(name=username)                if same_name_user:  # 用戶名唯一                    message = '用戶已經(jīng)存在,請(qǐng)重新選擇用戶名!'                    return render(request, 'login/register.html', locals())                same_email_user = models.User.objects.filter(email=email)                if same_email_user:  # 郵箱地址唯一                    message = '該郵箱地址已被注冊(cè),請(qǐng)使用別的郵箱!'                    return render(request, 'login/register.html', locals())                # 當(dāng)一切都OK的情況下,創(chuàng)建新用戶                new_user = models.User()                new_user.name = username                new_user.password = hash_code(password1)  # 使用加密密碼                new_user.email = email                new_user.sex = sex                new_user.save()                code = make_confirm_string(new_user)                send_email(email, code)                message = '請(qǐng)前往注冊(cè)郵箱,進(jìn)行郵件確認(rèn)!'                return render(request, 'login/confirm.html', locals())  # 跳轉(zhuǎn)到等待郵件確認(rèn)頁(yè)面。    register_form = forms.RegisterForm()    return render(request, 'login/register.html', locals())    def logout(request):    if not request.session.get('is_login', None):        # 如果本來(lái)就未登錄,也就沒(méi)有登出一說(shuō)        return redirect("/index/")    request.session.flush()    # 或者使用下面的方法    # del request.session['is_login']    # del request.session['user_id']    # del request.session['user_name']    return redirect("/index/")def user_confirm(request):    code = request.GET.get('code', None)    message = ''    try:        confirm = models.ConfirmString.objects.get(code=code)    except:        message = '無(wú)效的確認(rèn)請(qǐng)求!'        return render(request, 'login/confirm.html', locals())    c_time = confirm.c_time    now = datetime.datetime.now()    if now > c_time + datetime.timedelta(settings.CONFIRM_DAYS):        confirm.user.delete()        message = '您的郵件已經(jīng)過(guò)期!請(qǐng)重新注冊(cè)!'        return render(request, 'login/confirm.html', locals())    else:        confirm.user.has_confirmed = True        confirm.user.save()        confirm.delete()        message = '感謝確認(rèn),請(qǐng)使用賬戶登錄!'        return render(request, 'login/confirm.html', locals())

12.5.功能展示

首先,通過(guò)admin后臺(tái)刪除原來(lái)所有的用戶。

進(jìn)入注冊(cè)頁(yè)面,如下圖所示:

點(diǎn)擊提交,此時(shí)激活郵件已發(fā)送,但還是not confirmed狀態(tài),還不能登入,進(jìn)入你的測(cè)試郵箱,查看注冊(cè)郵件:

點(diǎn)擊鏈接,自動(dòng)跳轉(zhuǎn)到確認(rèn)成功提示頁(yè)面,2秒后再跳轉(zhuǎn)到登錄頁(yè)面。這個(gè)時(shí)候再次查看admin后臺(tái),可以看到用戶已經(jīng)處于登錄確認(rèn)狀態(tài),并且確認(rèn)碼也被自動(dòng)刪除了,不會(huì)第二次被使用:

使用該用戶正常登錄吧!Very Good!

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)
打開(kāi)APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
practical django project 第九章 代碼共享應(yīng)用中的表單處理
應(yīng)用技巧:如何基于Python Django實(shí)現(xiàn)驗(yàn)證碼登錄功能?
Django——auth
python測(cè)試開(kāi)發(fā)django-43.session機(jī)制(登錄/注銷)
Django工程的分層結(jié)構(gòu)
Django 簡(jiǎn)單實(shí)現(xiàn)登錄界面
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服