2020年9月26日星期六

019 Python编程 從入門到實踐, 第二部分  專案3 Web應用程式 第 19 章 用戶帳戶

申明本站飛宇網 https://feiyetopro.blogspot.com/自網路收集整理之書籍文章影音僅供預覽交流學習研究,其[書籍、文章、影音]情節內容, 評論屬其個人行為, 與本網站無關。版權歸原作者和出版社所有,請在下載 24 小時內刪除,不得用作商業用途;如果您喜歡其作品,請支持訂閱購買[正版]謝謝!


19 章 用戶帳戶

Web應用程式的核心是讓任何用戶都能夠註冊帳戶並能夠使用它,不管用戶身處何方。在本章中,你將創建一些表單,讓使用者能夠添加主題和條目,以及編輯既有的條目。你還將學習Django如何防範對基於表單的網頁發起的常見攻擊,這讓你無需花太多時間考慮確保應用程式安全的問題。

然後,我們將實現一個使用者身份驗證系統。你將創建一個註冊頁面,供使用者創建帳戶,並讓有些頁面只能供已登錄的用戶訪問。接下來,我們將修改一些視圖函數,使得使用者只能看到自己的資料。你將學習如何確保使用者資料的安全。

19.1 讓使用者能夠輸入資料

建立用於創建使用者帳戶的身份驗證系統之前,我們先來添加幾個頁面,讓使用者能夠輸入資料。我們將讓使用者能夠添加新主題、添加新條目以及編輯既有條目。

當前,只有超級使用者能夠通過管理網站輸入資料。我們不想讓用戶與管理網站交互,因此我們將使用Django的表單創建工具來創建讓使用者能夠輸入資料的頁面。

19.1.1 添加新主題

首先來讓使用者能夠添加新主題。創建基於表單的頁面的方法幾乎與前面創建網頁一樣:定義一個URL,編寫一個視圖函數並編寫一個範本。一個主要差別是,需要導入包含表單的模組forms.py

1. 用於添加主題的表單

讓使用者輸入並提交資訊的頁面都是表單,那怕它看起來不像表單。使用者輸入資訊時,我們需要進行驗證,確認提供的資訊是正確的資料類型,且不是惡意的資訊,如中斷伺服器的代碼。然後,我們再對這些有效資訊進行處理,並將其保存到資料庫的合適地方。這些工作很多都是由Django自動完成的。

Django中,創建表單的最簡單方式是使用ModelForm,它根據我們在第18章定義的模型中的資訊自動創建表單。創建一個名為forms.py的檔,將其存儲到models.py所在的目錄中,並在其中編寫你的第一個表單:

forms.py

 

  from django import forms

 

  from .models import Topic

 

class TopicForm(forms.ModelForm):

      class Meta:

         model = Topic

         fields = ['text']

         labels = {'text': ''}

 

 

 

 

 

 

 

 

我們首先導入了模組forms 以及要使用的模型Topic 。在處,我們定義了一個名為TopicForm 的類,它繼承了forms.ModelForm 

最簡單的ModelForm 版本只包含一個內嵌的Meta 類,它告訴Django根據哪個模型創建表單,以及在表單中包含哪些欄位。在處,我們根據模型Topic 創建一個表單,該表單只包含欄位text (見)。處的代碼讓Django不要為欄位text 生成標籤。

2. URL模式new_topic

這個新網頁的URL應簡短而具有描述性,因此當使用者要添加新主題時,我們將切換到http://localhost:8000/new_topic/。下面是網頁new_topic URL模式,我們將其添加到learning_logs/urls.py中:

urls.py

 

--snip--

urlpatterns = [

    --snip--

    # 用於添加新主題的網頁

    url(r'^new_topic/$', views.new_topic, name='new_topic'),

]

 

 

 

 

 

 

 

 

這個URL模式將請求交給視圖函數new_topic() ,接下來我們將編寫這個函數。

3. 視圖函數new_topic()

函數new_topic() 需要處理兩種情形:剛進入new_topic 網頁(在這種情況下,它應顯示一個空表單);對提交的表單數據進行處理,並將用戶重定向到網頁topics 

views.py

 

  from django.shortcuts import render

  from django.http import HttpResponseRedirect

  from django.core.urlresolvers import reverse

 

  from .models import Topic

  from .forms import TopicForm

 

  --snip--

  def new_topic(request):

      """添加新主題"""

     if request.method != 'POST':

          # 未提交資料:創建一個新表單

         form = TopicForm()

      else:

          # POST提交的資料,對資料進行處理

         form = TopicForm(request.POST)

         if form.is_valid():

             form.save()

             return HttpResponseRedirect(reverse('learning_logs:topics'))

 

     context = {'form': form}

      return render(request, 'learning_logs/new_topic.html', context)

 

 

 

 

 

 

 

 

我們導入了HttpResponseRedirect 類,使用者提交主題後我們將使用這個類將用戶重定向到網頁topics 。函數reverse() 根據指定的URL模型確定URL,這意味著Django將在頁面被請求時生成URL。我們還導入了剛才創建的表單TopicForm 

4. GET請求和POST請求

創建Web應用程式時,將用到的兩種主要請求類型是GET請求和POST請求。對於只是從伺服器讀取資料的頁面,使用GET請求;在使用者需要通過表單提交資訊時,通常使用POST請求。處理所有表單時,我們都將指定使用POST方法。還有一些其他類型的請求,但這個專案沒有使用。

函數new_topic() 將請求對象作為參數。用戶初次請求該網頁時,其流覽器將發送GET請求;用戶填寫並提交表單時,其流覽器將發送POST請求。根據請求的類型,我們可以確定用戶請求的是空表單(GET請求)還是要求對填寫好的表單進行處理(POST請求)。

處的測試確定請求方法是GET還是POST。如果請求方法不是POST,請求就可能是GET,因此我們需要返回一個空表單(即便請求是其他類型的,返回一個空表單也不會有任何問題)。我們創建一個TopicForm 實例(見),將其存儲在變數form 中,再通過上下文字典將這個表單發送給範本(見)。由於產生實體TopicForm 時我們沒有指定任何實參,Django將創建一個可供用戶填寫的空表單。

如果請求方法為POST,將執行else 代碼塊,對提交的表單數據進行處理。我們使用使用者輸入的資料(它們存儲在request.POST 中)創建一個TopicForm 實例(見),這樣物件form 將包含使用者提交的資訊。

要將提交的資訊保存到資料庫,必須先通過檢查確定它們是有效的(見)。函數is_valid() 核實使用者填寫了所有必不可少的欄位(表單字段默認都是必不可少的),且輸入的資料與要求的欄位類型一致(例如,欄位text 少於200個字元,這是我們在第18章中的models.py中指定的)。這種自動驗證避免了我們去做大量的工作。如果所有欄位都有效,我們就可調用save() (見),將表單中的資料寫入資料庫。保存資料後,就可離開這個頁面了。我們使用reverse() 獲取頁面topics URL,並將其傳遞給HttpResponseRedirect() (見),後者將用戶的流覽器重定向到頁面topics 。在頁面topics 中,使用者將在主題清單中看到他剛輸入的主題。

5. 範本new_topic

下麵來創建新範本new_topic.html,用於顯示我們剛創建的表單:

new_topic.html

 

  {% extends "learning_logs/base.html" %}

 

  {% block content %}

    <p>Add a new topic:</p>

 

   <form action="{% url 'learning_logs:new_topic' %}" method='post'>

     {% csrf_token %}

     {{ form.as_p }}

     <button name="submit">add topic</button>

    </form>

 

  {% endblock content %}

 

 

 

 

 

 

 

 

這個範本繼承了base.html,因此其基本結構與專案學習筆記的其他頁面相同。在處,我們定義了一個HTML表單。實參action 告訴伺服器將提交的表單數據發送到哪裡,這裡我們將它發回給視圖函數new_topic() 。實參method 讓流覽器以POST請求的方式提交資料。

Django使用範本標籤{% csrf_token %} (見)來防止攻擊者利用表單來獲得對伺服器未經授權的訪問(這種攻擊被稱為跨站請求偽造 )。在處,我們顯示表單,從中可知Django使得完成顯示表單等任務有多簡單:我們只需包含範本變數{{ form.as_p }} ,就可讓Django自動創建顯示表單所需的全部欄位。修飾符as_p Django以段落格式渲染所有表單元素,這是一種整潔地顯示表單的簡單方式。

Django不會為表單創建提交按鈕,因此我們在處定義了一個這樣的按鈕。

6. 連結到頁面new_topic

接下來,我們在頁面topics 中添加一個到頁面new_topic 的連結:

topics.html

 

{% extends "learning_logs/base.html" %}

 

{% block content %}

 

  <p>Topics</p>

 

  <ul>

    --snip--

  </ul>

 

  <a href="{% url 'learning_logs:new_topic' %}">Add a new topic:</a>

 

{% endblock content %}

 

 

 

 

 

 

 

 

這個連結放在了既有主題清單的後面。圖19-1顯示了生成的表單。請使用這個表單來添加幾個新主題。


19-1 用於添加新主題的頁面

19.1.2 添加新條目

現在使用者可以添加新主題了,但他們還想添加新條目。我們將再次定義URL,編寫視圖函數和範本,並連結到添加新條目的網頁。但在此之前,我們需要在forms.py中再添加一個類。

1. 用於添加新條目的表單

我們需要創建一個與模型Entry 相關聯的表單,但這個表單的定制程度比TopicForm 要高些:

forms.py

 

  from django import forms

 

  from .models import Topic, Entry

 

  class TopicForm(forms.ModelForm):

      --snip--

 

  class EntryForm(forms.ModelForm):

      class Meta:

          model = Entry

          fields = ['text']

         labels = {'text': ''}

         widgets = {'text': forms.Textarea(attrs={'cols': 80})}

 

 

 

 

 

 

 

 

我們首先修改了import 語句,使其除導入Topic 外,還導入Entry 。新類EntryForm 繼承了forms.ModelForm ,它包含的Meta 類指出了表單基於的模型以及要在表單中包含哪些欄位。這裡也給欄位'text' 指定了一個空標籤(見

處,我們定義了屬性widgets 小部件 widget)是一個HTML表單元素,如單行文字方塊、多行文本區域或下拉清單。通過設置屬性widgets ,可覆蓋Django選擇的默認小部件。通過讓Django使用forms.Textarea ,我們定制了欄位'text' 的輸入小部件,將文本區域的寬度設置為80列,而不是默認的40列。這給用戶提供了足夠的空間,可以編寫有意義的條目。

2. URL模式new_entry

在用於添加新條目的頁面的URL模式中,需要包含實參topic_id ,因為條目必須與特定的主題相關聯。該URL模式如下,我們將它添加到了learning_logs/urls.py中:

urls.py

 

--snip--

urlpatterns = [

    --snip--

    # 用於添加新條目的頁面

    url(r'^new_entry/(?P<topic_id>\d+)/$', views.new_entry, name='new_entry'),

]

 

 

 

 

 

 

 

 

這個URL模式與形式為http://localhost:8000/new_entry/id / URL匹配,其中 id 是一個與主題ID匹配的數位。代碼(?P<topic_id>\d+) 捕獲一個數字值,並將其存儲在變數topic_id 中。請求的URL與這個模式匹配時,Django將請求和主題ID發送給函數new_entry() 

3. 視圖函數new_entry()

視圖函數new_entry() 與函數new_topic() 很像:

views.py

 

  from django.shortcuts import render

  --snip--

 

  from .models import Topic

  from .forms import TopicForm, EntryForm

 

  --snip--

  def new_entry(request, topic_id):

      """在特定的主題中添加新條目"""

     topic = Topic.objects.get(id=topic_id)

 

     if request.method != 'POST':

          # 未提交資料,創建一個空表單

         form = EntryForm()

      else:

          # POST提交的資料,對資料進行處理

         form = EntryForm(data=request.POST)

          if form.is_valid():

             new_entry = form.save(commit=False)

             new_entry.topic = topic

              new_entry.save()

             return HttpResponseRedirect(reverse('learning_logs:topic',

                                          args=[topic_id]))

 

      context = {'topic': topic, 'form': form}

      return render(request, 'learning_logs/new_entry.html', context)

 

 

 

 

 

 

 

 

我們修改了import 語句,在其中包含了剛創建的EntryForm new_entry() 的定義包含形參topic_id ,用於存儲從URL中獲得的值。渲染頁面以及處理表單數據時,都需要知道針對的是哪個主題,因此我們使用topic_id 來獲得正確的主題(見

處,我們檢查請求方法是POST還是GET。如果是GET請求,將執行if 代碼塊:創建一個空的EntryForm 實例(見)。如果請求方法為POST,我們就對資料進行處理:創建一個EntryForm 實例,使用request 物件中的POST資料來填充它(見);再檢查表單是否有效,如果有效,就設置條目物件的屬性topic ,再將條目物件保存到資料庫。

調用save() 時,我們傳遞了實參commit=False (見),Django創建一個新的條目物件,並將其存儲到new_entry 中,但不將它保存到資料庫中。我們將new_entry 的屬性topic 設置為在這個函數開頭從資料庫中獲取的主題(見),然後調用save() ,且不指定任何實參。這將把條目保存到資料庫,並將其與正確的主題相關聯。

處,我們將使用者重定向到顯示相關主題的頁面。調用reverse() 時,需要提供兩個實參:要根據它來生成URLURL模式的名稱;列表args ,其中包含要包含在URL中的所有實參。在這裡,清單args 只有一個元素——topic_id 。接下來,調用HttpResponseRedirect() 將使用者重定向到顯示新增條目所屬主題的頁面,使用者將在該頁面的條目清單中看到新添加的條目。

4. 範本new_entry

從下面的代碼可知,範本new_entry 類似於範本new_topic 

new_entry.html

 

  {% extends "learning_logs/base.html" %}

 

  {% block content %}

 

   <p><a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a></p>

 

    <p>Add a new entry:</p>

   <form action="{% url 'learning_logs:new_entry' topic.id %}" method='post'>

      {% csrf_token %}

      {{ form.as_p }}

      <button name='submit'>add entry</button>

    </form>

 

  {% endblock content %}

 

 

 

 

 

 

 

 

我們在頁面頂端顯示了主題(見),讓用戶知道他是在哪個主題中添加條目;該主題名也是一個連結,可用於返回到該主題的主頁面。

表單的實參action 包含URL中的topic_id 值,讓視圖函數能夠將新條目關聯到正確的主題(見)。除此之外,這個範本與範本new_topic.html完全相同。

5. 連結到頁面new_entry

接下來,我們需要在顯示特定主題的頁面中添加到頁面new_entry 的連結:

topic.html

 

{% extends "learning_logs/base.html" %}

 

{% block content %}

 

  <p>Topic: {{ topic }}</p>

 

  <p>Entries:</p>

  <p>

    <a href="{% url 'learning_logs:new_entry' topic.id %}">add new entry</a>

  </p>

  <ul>

  --snip—

  </ul>

 

{% endblock content %}

 

 

 

 

 

 

 

 

我們在顯示條目前添加連結,因為在這種頁面中,執行的最常見的操作是添加新條目。圖19-2顯示了頁面new_entry 。現在使用者可以添加新主題,還可以在每個主題中添加任意數量的條目。請在一些既有主題中添加一些新條目,嘗試使用一下頁面new_entry 


19-2 頁面new_entry

19.1.3 編輯條目

下面來創建一個頁面,讓使用者能夠編輯既有的條目。

1. URL模式edit_entry

這個頁面的URL需要傳遞要編輯的條目的ID。修改後的learning_logs/urls.py如下:

urls.py

 

--snip--

urlpatterns = [

    --snip--

    # 用於編輯條目的頁面

    url(r'^edit_entry/(?P<entry_id>\d+)/$', views.edit_entry,

        name='edit_entry'),

]

 

 

 

 

 

 

 

 

URL(如http://localhost:8000/edit_entry/1/)中傳遞的ID存儲在形參entry_id 中。這個URL模式將預期匹配的請求發送給視圖函數edit_entry() 

2. 視圖函數edit_entry()

頁面edit_entry 收到GET請求時,edit_entry() 將返回一個表單,讓用戶能夠對條目進行編輯。該頁面收到POST請求(條目文本經過修訂)時,它將修改後的文本保存到資料庫中:

views.py

 

  from django.shortcuts import render

  --snip--

 

  from .models import Topic, Entry

  from .forms import TopicForm, EntryForm

  --snip--

 

  def edit_entry(request, entry_id):

      """編輯既有條目"""

     entry = Entry.objects.get(id=entry_id)

      topic = entry.topic

 

      if request.method != 'POST':

          # 初次請求,使用當前條目填充表單

         form = EntryForm(instance=entry)

      else:

          # POST提交的資料,對資料進行處理

         form = EntryForm(instance=entry, data=request.POST)

          if form.is_valid():

             form.save()

             return HttpResponseRedirect(reverse('learning_logs:topic',

                                          args=[topic.id]))

 

      context = {'entry': entry, 'topic': topic, 'form': form}

      return render(request, 'learning_logs/edit_entry.html', context)

 

 

 

 

 

 

 

 

我們首先需要導入模型Entry 。在處,我們獲取使用者要修改的條目物件,以及與該條目相關聯的主題。在請求方法為GET時將執行的if 代碼塊中,我們使用實參instance=entry 創建一個EntryForm 實例(見)。這個實參讓Django創建一個表單,並使用既有條目物件中的資訊填充它。使用者將看到既有的資料,並能夠編輯它們。

處理POST請求時,我們傳遞實參instance=entry data=request.POST (見),Django根據既有條目物件創建一個表單實例,並根據request.POST 中的相關資料對其進行修改。然後,我們檢查表單是否有效,如果有效,就調用save() ,且不指定任何實參(見)。接下來,我們重定向到顯示條目所屬主題的頁面(見),使用者將在其中看到其編輯的條目的新版本。

3. 範本edit_entry

下麵是範本edit_entry.html,它與範本new_entry.html類似:

edit_entry.html

 

  {% extends "learning_logs/base.html" %}

 

  {% block content %}

 

    <p><a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a></p>

 

    <p>Edit entry:</p>

 

   <form action="{% url 'learning_logs:edit_entry' entry.id %}" method='post'>

      {% csrf_token %}

      {{ form.as_p }}

     <button name="submit">save changes</button>

    </form>

 

  {% endblock content %}

 

 

 

 

 

 

 

 

處,實參action 將表單發回給函數edit_entry() 進行處理。在標籤{% url %} 中,我們將條目ID作為一個實參,讓視圖物件能夠修改正確的條目物件。我們將提交按鈕命名為save changes,以提醒用戶:按一下該按鈕將保存所做的編輯,而不是創建一個新條目(見

4. 連結到頁面edit_entry

現在,在顯示特定主題的頁面中,需要給每個條目添加到頁面edit_entry 的連結:

topic.html

 

--snip--

  {% for entry in entries %}

    <li>

      <p>{{ entry.date_added|date:'M d, Y H:i' }}</p>

      <p>{{ entry.text|linebreaks }}</p>

      <p>

        <a href="{% url 'learning_logs:edit_entry' entry.id %}">edit entry</a>

      </p>

    </li>

--snip--

 

 

 

 

 

 

 

 

我們將編輯連結放在每個條目的日期和文本後面。在迴圈中,我們使用範本標籤{% url %} 根據URL模式edit_entry 和當前條目的ID屬性(entry.id )來確定URL。連結文本為"edit entry" ,它出現在頁面中每個條目的後面。圖19-3顯示了包含這些連結時,顯示特定主題的頁面是什麼樣的。


19-3 每個條目都有一個用於對其進行編輯的連結

至此,學習筆記已具備了需要的大部分功能。使用者可添加主題和條目,還可根據需要查看任何一組條目。在下一節,我們將實現一個使用者註冊系統,讓任何人都可向學習筆記申請帳戶,並創建自己的主題和條目。

動手試一試

19-1 博客 :新建一個Django項目,將其命名為Blog。在這個專案中,創建一個名為blogs的應用程式,並在其中創建一個名為BlogPost 的模型。這個模型應包含title text date_added 等欄位。為這個專案創建一個超級用戶,並使用管理網站創建幾個簡短的帖子。創建一個主頁,在其中按時間順序顯示所有的帖子。

創建兩個表單,其中一個用於發佈新帖子,另一個用於編輯既有的帖子。

嘗試填寫這些表單,確認它們能夠正確地工作。

19.2 創建用戶帳戶

在這一節,我們將建立一個使用者註冊和身份驗證系統,讓使用者能夠註冊帳戶,進而登錄和註銷。我們將創建一個新的應用程式,其中包含與處理用戶帳戶相關的所有功能。我們還將對模型Topic 稍做修改,讓每個主題都歸屬於特定使用者。

19.2.1 應用程式users

我們首先使用命令startapp 來創建一個名為users 的應用程式:

 

  (ll_env)learning_log$ python manage.py startapp users

  (ll_env)learning_log$ ls

db.sqlite3  learning_log  learning_logs  ll_env  manage.py  users

  (ll_env)learning_log$ ls users

admin.py __init__.py migrations models.py tests.py views.py

 

 

 

 

 

 

 

 

這個命令新建一個名為users的目錄(見),其結構與應用程式learning_logs 相同(見

1. 將應用程式users 添加到settings.py

settings.py中,我們需要將這個新的應用程式添加到INSTALLED_APPS 中,如下所示:

settings.py

 

--snip--

INSTALLED_APPS = (

    --snip--

    # 我的應用程式

    'learning_logs',

    'users',

)

--snip--

 

 

 

 

 

 

 

 

這樣,Django將把應用程式users 包含到專案中。

2. 包含應用程式users URL

接下來,我們需要修改專案根目錄中的urls.py,使其包含我們將為應用程式users 定義的URL

urls.py

 

from django.conf.urls import include, url

from django.contrib import admin

 

urlpatterns = [

    url(r'^admin/', include(admin.site.urls)),

    url(r'^users/', include('users.urls', namespace='users')),

    url(r'', include('learning_logs.urls', namespace='learning_logs')),

]

 

 

 

 

 

 

 

 

我們添加了一行代碼,以包含應用程式users 中的檔urls.py。這行代碼與任何以單詞users打頭的URL(如http://localhost:8000/users/login/)都匹配。我們還創建了命名空間'users' ,以便將應用程式learning_logs URL同應用程式users URL區分開來。

19.2.2 登錄頁面

我們首先來實現登錄頁面的功能。為此,我們將使用Django提供的默認登錄視圖,因此URL模式會稍有不同。在目錄learning_log/users/中,新建一個名為urls.py的檔,並在其中添加如下代碼:

urls.py

 

  """為應用程式users定義URL模式"""

 

  from django.conf.urls import url

from django.contrib.auth.views import login

 

  from . import views

 

  urlpatterns = [

      # 登錄頁面

     url(r'^login/$', login, {'template_name': 'users/login.html'},

          name='login'),

  ]

 

 

 

 

 

 

 

 

我們首先導入了默認視圖login (見)。登錄頁面的URL模式與URL http://localhost:8000/users/login/匹配(見)。這個URL中的單詞usersDjangousers/urls.py中查找,而單詞login讓它將請求發送給Django默認視圖login (請注意,視圖實參為login ,而不是views.login )。鑒於我們沒有編寫自己的視圖函數,我們傳遞了一個字典,告訴Django去哪裡查找我們將編寫的範本。這個範本包含在應用程式users 而不是learning_logs 中。

1. 範本login.html

使用者請求登錄頁面時,Django將使用其默認視圖login ,但我們依然需要為這個頁面提供範本。為此,在目錄learning_log/users/中,創建一個名為templates的目錄,並在其中創建一個名為users的目錄。以下是範本login.html,你應將其存儲到目錄learning_log/users/templates/users/中:

login.html

 

  {% extends "learning_logs/base.html" %}

 

  {% block content %}

 

   {% if form.errors %}

    <p>Your username and password didn't match. Please try again.</p>

    {% endif %}

 

   <form method="post" action="{% url 'users:login' %}">

    {% csrf_token %}

   {{ form.as_p }}

 

   <button name="submit">log in</button>

   <input type="hidden" name="next" value="{% url 'learning_logs:index' %}" />

    </form>

 

  {% endblock content %}

 

 

 

 

 

 

 

 

這個範本繼承了base.html,旨在確保登錄頁面的外觀與網站的其他頁面相同。請注意,一個應用程式中的範本可繼承另一個應用程式中的範本。

如果表單的errors 屬性被設置,我們就顯示一條錯誤消息(見),指出輸入的用戶名密碼對與資料庫中存儲的任何用戶名密碼對都不匹配。

我們要讓登錄視圖處理表單,因此將實參action 設置為登錄頁面的URL(見)。登錄視圖將一個表單發送給範本,在範本中,我們顯示這個表單(見)並添加一個提交按鈕(見)。在處,我們包含了一個隱藏的表單元素——'next' ,其中的實參value 告訴Django在用戶成功登錄後將其重定向到什麼地方——在這裡是主頁。

2. 連結到登錄頁面

下麵在base.html中添加到登錄頁面的連結,讓所有頁面都包含它。使用者已登錄時,我們不想顯示這個連結,因此將它嵌套在一個{% if %} 標籤中:

base.html

 

  <p>

    <a href="{% url 'learning_logs:index' %}">Learning Log</a> -

    <a href="{% url 'learning_logs:topics' %}">Topics</a> -

   {% if user.is_authenticated %}

     Hello, {{ user.username }}.

    {% else %}

     <a href="{% url 'users:login' %}">log in</a>

    {% endif %}

  </p>

 

  {% block content %}{% endblock content %}

 

 

 

 

 

 

 

 

Django身份驗證系統中,每個範本都可使用變數user ,這個變數有一個is_authenticated 屬性:如果使用者已登錄,該屬性將為True ,否則為False 。這讓你能夠向已通過身份驗證的使用者顯示一條消息,而向未通過身份驗證的使用者顯示另一條消息。

在這裡,我們向已登錄的使用者顯示一條問候語(見)。對於已通過身份驗證的使用者,還設置了屬性username ,我們使用這個屬性來個性化問候語,讓用戶知道他已登錄(見)。在處,對於還未通過身份驗證的使用者,我們再顯示一個到登錄頁面的連結。

3. 使用登錄頁面

前面建立了一個用戶帳戶,下面來登錄一下,看看登錄頁面是否管用。請訪問http://localhost:8000/admin/,如果你依然是以管理員的身份登錄的,請在頁眉上找到登出連結並按一下它。

註銷後,訪問http://localhost:8000/users/login/,你將看到類似於圖19-4所示的登錄頁面。輸入你在前面設置的用戶名和密碼,將進入頁面index。。在這個主頁的頁眉中,顯示了一條個性化問候語,其中包含你的用戶名。


19-4 登錄頁面

19.2.3 註銷

現在需要提供一個讓使用者登出的途徑。我們不創建用於登出的頁面,而讓使用者只需按一下一個連結就能登出並返回到主頁。為此,我們將為登出連結定義一個URL模式,編寫一個視圖函數,並在base.html中添加一個登出連結。

1. 註銷URL

下面的代碼為登出定義了URL模式,該模式與URL http://locallwst:8000/users/logout/匹配。修改後的users/urls.py如下:

urls.py

 

--snip--

urlpatterns = [

    # 登錄頁面

    --snip--

    # 註銷

    url(r'^logout/$', views.logout_view, name='logout'),

]

 

 

 

 

 

 

 

 

這個URL模式將請求發送給函數logout_view() 。這樣給這個函數命名,旨在將其與我們將在其中調用的函數logout() 區分開來(請確保你修改的是users/urls.py,而不是learning_log/ urls.py)。

2. 視圖函數logout_view()

函數logout_view() 很簡單:只是導入Django函數logout() ,並調用它,再重定向到主頁。請打開users/views.py,並輸入下面的代碼:

views.py

 

  from django.http import HttpResponseRedirect

  from django.core.urlresolvers import reverse

from django.contrib.auth import logout

 

  def logout_view(request):

      """註銷用戶"""

     logout(request)

     return HttpResponseRedirect(reverse('learning_logs:index'))

 

 

 

 

 

 

 

 

我們從django.contrib.auth中導入了函數logout() (見)。在處,我們調用了函數logout() ,它要求將request 對象作為實參。然後,我們重定向到主頁(見

3. 連結到登出視圖

現在我們需要添加一個登出連結。我們在base.html中添加這種連結,讓每個頁面都包含它;我們將它放在標籤{% if user.is_authenticated %} 中,使得僅當用戶登錄後才能看到它:

base.html

 

--snip—

  {% if user.is_authenticated %}

    Hello, {{ user.username }}.

    <a href="{% url 'users:logout' %}">log out</a>

  {% else %}

    <a href="{% url 'users:login' %}">log in</a>

  {% endif %}

--snip--

 

 

 

 

 

 

 

 

19-5顯示了使用者登錄後看到的主頁。這裡的重點是創建能夠正確工作的網站,因此幾乎沒有設置任何樣式。確定所需的功能都能正確運行後,我們將設置這個網站的樣式,使其看起來更專業。


19-5 包含個性化問候語和登出連結的主頁

19.2.4 註冊頁面

下面來創建一個讓新使用者能夠註冊的頁面。我們將使用Django提供的表單UserCreationForm ,但編寫自己的視圖函數和範本。

1. 註冊頁面的URL模式

下面的代碼定義了註冊頁面的URL模式,它也包含在users/urls.py中:

urls.py

 

--snip--

urlpatterns = [

    # 登錄頁面

    --snip--

    # 註冊頁面

    url(r'^register/$', views.register, name='register'),

]

 

 

 

 

 

 

 

 

這個模式與URL http://localhost:8000/users/register/匹配,並將請求發送給我們即將編寫的函數register() 

2. 視圖函數register()

在註冊頁面首次被請求時,視圖函數register() 需要顯示一個空的註冊表單,並在用戶提交填寫好的註冊表單時對其進行處理。如果註冊成功,這個函數還需讓使用者自動登錄。請在users/views.py中添加如下代碼:

views.py

 

  from django.shortcuts import render

  from django.http import HttpResponseRedirect

  from django.core.urlresolvers import reverse

  from django.contrib.auth import login, logout, authenticate

  from django.contrib.auth.forms import UserCreationForm

 

  def logout_view(request):

  --snip--

 

  def register(request):

      """註冊新用戶"""

      if request.method != 'POST':

          # 顯示空的註冊表單

         form = UserCreationForm()

      else:

          # 處理填寫好的表單

         form = UserCreationForm(data=request.POST)

 

         if form.is_valid():

             new_user = form.save()

              # 讓用戶自動登錄,再重定向到主頁

             authenticated_user = authenticate(username=new_user.username,

                  password=request.POST['password1'])

             login(request, authenticated_user)

             return HttpResponseRedirect(reverse('learning_logs:index'))

 

      context = {'form': form}

      return render(request, 'users/register.html', context)

 

 

 

 

 

 

 

 

我們首先導入了函數render() ,然後導入了函數login() authenticate() ,以便在用戶正確地填寫了註冊資訊時讓其自動登錄。我們還導入了默認表單UserCreationForm 。在函數register() 中,我們檢查要回應的是否是POST請求。如果不是,就創建一個UserCreationForm 實例,且不給它提供任何初始資料(見

如果回應的是POST請求,我們就根據提交的資料創建一個UserCreationForm 實例(見),並檢查這些資料是否有效:就這裡而言,是用戶名未包含非法字元,輸入的兩個密碼相同,以及使用者沒有試圖做惡意的事情。

如果提交的資料有效,我們就調用表單的方法save() ,將用戶名和密碼的散列值保存到資料庫中(見)。方法save() 返回新創建的使用者物件,我們將其存儲在new_user 中。

保存使用者的資訊後,我們讓使用者自動登錄,這包含兩個步驟。首先,我們調用authenticate() ,並將實參new_user.username 和密碼傳遞給它(見)。使用者註冊時,被要求輸入密碼兩次;由於表單是有效的,我們知道輸入的這兩個密碼是相同的,因此可以使用其中任何一個。在這裡,我們從表單的POST資料中獲取與鍵'password1' 相關聯的值。如果用戶名和密碼無誤,方法authenticate() 將返回一個通過了身份驗證的使用者物件,而我們將其存儲在authenticated_user 中。接下來,我們調用函數login() ,並將物件request authenticated_user 傳遞給它(見),這將為新用戶創建有效的會話。最後,我們將用戶重定向到主頁(見),其頁眉中顯示了一條個性化的問候語,讓用戶知道註冊成功了。

3. 註冊範本

註冊頁面的範本與登錄頁面的範本類似,請務必將其保存到login.html所在的目錄中:

register.html

 

{% extends "learning_logs/base.html" %}

 

{% block content %}

 

  <form method="post" action="{% url 'users:register' %}">

    {% csrf_token %}

    {{ form.as_p }}

 

    <button name="submit">register</button>

    <input type="hidden" name="next" value="{% url 'learning_logs:index' %}" />

  </form>

 

{% endblock content %}

 

 

 

 

 

 

 

 

這裡也使用了方法as_p ,讓Django在表單中正確地顯示所有的欄位,包括錯誤消息——如果使用者沒有正確地填寫表單。

4. 連結到註冊頁面

接下來,我們添加這樣的代碼,即在使用者沒有登錄時顯示到註冊頁面的連結:

base.html

 

--snip--

  {% if user.is_authenticated %}

    Hello, {{ user.username }}.

    <a href="{% url 'users:logout' %}">log out</a>

  {% else %}

    <a href="{% url 'users:register' %}">register</a> -

    <a href="{% url 'users:login' %}">log in</a>

  {% endif %}

--snip--

 

 

 

 

 

 

 

 

現在,已登錄的用戶看到的是個性化的問候語和登出連結,而未登錄的使用者看到的是註冊連結和登錄連結。請嘗試使用註冊頁面創建幾個用戶名各不相同的用戶帳戶。

在下一節,我們將對一些頁面進行限制,僅讓已登錄的用戶訪問它們,我們還將確保每個主題都屬於特定使用者。

注意  這裡的註冊系統允許使用者創建任意數量的帳戶。有些系統要求使用者確認其身份:發送一封確認郵件,用戶回復後其帳戶才生效。通過這樣做,系統生成的垃圾帳戶將比這裡使用的簡單系統少。然而,學習創建應用程式時,完全可以像這裡所做的那樣,使用簡單的使用者註冊系統。

 

動手試一試

19-2 博客帳戶 :在你為完成練習19-1而開發的專案Blog中,添加一個使用者身份驗證和註冊系統。讓已登錄的使用者在螢幕上看到其用戶名,並讓未註冊的使用者看到一個到註冊頁面的連結。

19.3 讓使用者擁有自己的資料

使用者應該能夠輸入其專有的資料,因此我們將創建一個系統,確定各項資料所屬的使用者,再限制對頁面的訪問,讓使用者只能使用自己的資料。

在本節中,我們將修改模型Topic ,讓每個主題都歸屬於特定使用者。這也將影響條目,因為每個條目都屬於特定的主題。我們先來限制對一些頁面的訪問。

19.3.1 使用@login_required 限制訪問

Django提供了裝飾器@login_required ,讓你能夠輕鬆地實現這樣的目標:對於某些頁面,只允許已登錄的用戶訪問它們。裝飾器 decorator)是放在函式定義前面的指令,Python在函數運行前,根據它來修改函數代碼的行為。下面來看一個示例。

1. 限制對topics 頁面的訪問

每個主題都歸特定使用者所有,因此應只允許已登錄的使用者請求topics 頁面。為此,在learning_logs/views.py中添加如下代碼:

views.py

 

--snip--

from django.core.urlresolvers import reverse

from django.contrib.auth.decorators import login_required

 

from .models import Topic, Entry

--snip--

 

@login_required

def topics(request):

    """顯示所有的主題"""

    --snip--

 

 

 

 

 

 

 

 

我們首先導入了函數login_required() 。我們將login_required() 作為裝飾器用於視圖函數topics() ——在它前面加上符號@ login_required ,讓Python在運行topics() 的代碼前先運行login_required() 的代碼。

login_required() 的代碼檢查用戶是否已登錄,僅當用戶已登錄時,Django才運行topics() 的代碼。如果使用者未登錄,就重定向到登錄頁面。

為實現這種重定向,我們需要修改settings.py,讓Django知道到哪裡去查找登錄頁面。請在settings.py末尾添加如下代碼:

settings.py

 

"""

專案learning_logDjango設置

--snip--

 

# 我的設置

LOGIN_URL = '/users/login/'

 

 

 

 

 

 

 

 

現在,如果未登錄的用戶請求裝飾器@login_required 的保護頁面,Django將重定向到settings.py中的LOGIN_URL 指定的URL

要測試這個設置,可註銷並進入主頁。然後,按一下連結Topics,這將重定向到登錄頁面。接下來,使用你的帳戶登錄,並再次按一下主頁中的Topics連結,你將看到topics頁面。

2. 全面限制對專案學習筆記的訪問

Django讓你能夠輕鬆地限制對頁面的訪問,但你必須針對要保護哪些頁面做出決定。最好先確定專案的哪些頁面不需要保護,再限制對其他所有頁面的訪問。你可以輕鬆地修改過於嚴格的訪問限制,其風險比不限制對敏感頁面的訪問更低。

在專案學習筆記中,我們將不限制對主頁、註冊頁面和登出頁面的訪問,並限制對其他所有頁面的訪問。

在下麵的learning_logs/views.py中,對除index() 外的每個視圖都應用了裝飾器@login_required 

views.py

 

--snip--

@login_required

def topics(request):

    --snip--

 

@login_required

def topic(request, topic_id):

    --snip--

 

@login_required

def new_topic(request):

    --snip--

 

@login_required

def new_entry(request, topic_id):

    --snip--

 

@login_required

def edit_entry(request, entry_id):

    --snip--

 

 

 

 

 

 

 

 

如果你在未登錄的情況下嘗試訪問這些頁面,將被重定向到登錄頁面。另外,你還不能按一下到new_topic 等頁面的連結。但如果你輸入URL http://localhost:8000/new_topic/,將重定向到登錄頁面。對於所有與私有使用者資料相關的URL,都應限制對它們的訪問。

19.3.2 將資料關聯到使用者

現在,需要將資料關聯到提交它們的用戶。我們只需將最高層的資料關聯到使用者,這樣更低層的資料將自動關聯到使用者。例如,在專案學習筆記中,應用程式的最高層資料是主題,而所有條目都與特定主題相關聯。只要每個主題都歸屬於特定使用者,我們就能確定資料庫中每個條目的所有者。

下面來修改模型Topic ,在其中添加一個關聯到用戶的外鍵。這樣做後,我們必須對資料庫進行遷移。最後,我們必須對有些視圖進行修改,使其只顯示與當前登錄的使用者相關聯的資料。

1. 修改模型Topic

models.py的修改只涉及兩行代碼:

models.py

 

from django.db import models

from django.contrib.auth.models import User

 

class Topic(models.Model):

   """使用者要學習的主題"""

    text = models.CharField(max_length=200)

    date_added = models.DateTimeField(auto_now_add=True)

    owner = models.ForeignKey(User)

 

    def __str__(self):

        """返回模型的字串表示"""

        return self.text

 

class Entry(models.Model):

    --snip--

 

 

 

 

 

 

 

 

我們首先導入了django.contrib.auth 中的模型User ,然後在Topic 中添加了欄位owner ,它建立到模型User 的外鍵關係。

2. 確定當前有哪些用戶

我們遷移資料庫時,Django將對資料庫進行修改,使其能夠存儲主題和使用者之間的關聯。為執行遷移,Django需要知道該將各個既有主題關聯到哪個使用者。最簡單的辦法是,將既有主題都關聯到同一個用戶,如超級用戶。為此,我們需要知道該使用者的ID

下面來查看已創建的所有用戶的ID。為此,啟動一個Django shell會話,並執行如下命令:

 

  (venv)learning_log$ python manage.py shell

>>> from django.contrib.auth.models import User

>>> User.objects.all()

  [<User: ll_admin>, <User: eric>, <User: willie>]

>>> for user in User.objects.all():

  ...     print(user.username, user.id)

  ...

  ll_admin 1

  eric 2

  willie 3

  >>>

 

 

 

 

 

 

 

 

處,我們在shell會話中導入了模型User 。然後,我們查看到目前為止都創建了哪些用戶(見)。輸出中列出了三個用戶:ll_adminericwillie

處,我們遍歷用戶列表,並列印每位用戶的用戶名和IDDjango詢問要將既有主題關聯到哪個使用者時,我們將指定其中的一個ID值。

3. 遷移資料庫

知道使用者ID後,就可以遷移資料庫了。

 

(venv)learning_log$ python manage.py makemigrations learning_logs

You are trying to add a non-nullable field 'owner' to topic without a default;

  we can't do that (the database needs something to populate existing rows).

Please select a fix:

   1) Provide a one-off default now (will be set on all existing rows)

   2) Quit, and let me add a default in models.py

Select an option: 1

Please enter the default value now, as valid Python

  The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now()

>>> 1

  Migrations for 'learning_logs':

    0003_topic_owner.py:

      - Add field owner to topic

 

 

 

 

 

 

 

 

我們首先執行了命令makemigrations (見)。在處的輸出中,Django指出我們試圖給既有模型Topic 添加一個必不可少(不可為空)的欄位,而該欄位沒有預設值。在處,Django給我們提供了兩種選擇:要麼現在提供預設值,要麼退出並在models.py中添加預設值。在處,我們選擇了第一個選項,因此Django讓我們輸入預設值(見

為將所有既有主題都關聯到管理使用者ll_admin,我輸入了用戶ID1(見)。並非必須使用超級用戶,而可使用已創建的任何用戶的ID。接下來,Django使用這個值來遷移資料庫,並生成了遷移檔0003_topic_owner.py,它在模型Topic 中添加欄位owner 

現在可以執行遷移了。為此,在活動的虛擬環境中執行下面的命令:

 

  (venv)learning_log$ python manage.py migrate

  Operations to perform:

    Synchronize unmigrated apps: messages, staticfiles

    Apply all migrations: learning_logs, contenttypes, sessions, admin, auth

  --snip--

  Running migrations:

    Rendering model states... DONE

   Applying learning_logs.0003_topic_owner... OK

  (venv)learning_log$

 

 

 

 

 

 

 

 

Django應用新的遷移,結果一切順利(見

為驗證遷移符合預期,可在shell會話中像下面這樣做:

 

>>> from learning_logs.models import Topic

>>> for topic in Topic.objects.all():

  ...     print(topic, topic.owner)

  ...

  Chess ll_admin

  Rock Climbing ll_admin

  >>>

 

 

 

 

 

 

 

 

我們從learning_logs.models 中導入Topic (見),再遍歷所有的既有主題,並列印每個主題及其所屬的使用者(見)。正如你看到的,現在每個主題都屬於使用者ll_admin

注意  你可以重置資料庫而不是遷移它,但如果這樣做,既有的資料都將丟失。一種不錯的做法是,學習如何在遷移資料庫的同時確保使用者資料的完整性。如果你確實想要一個全新的資料庫,可執行命令python manage.py flush ,這將重建資料庫的結構。如果你這樣做,就必須重新創建超級用戶,且原來的所有資料都將丟失。

19.3.3 只允許使用者訪問自己的主題

當前,不管你以哪個用戶的身份登錄,都能夠看到所有的主題。我們來改變這種情況,只向使用者顯示屬於自己的主題。

views.py中,對函數topics() 做如下修改:

views.py

 

--snip--

@login_required

def topics(request):

    """顯示所有的主題"""

    topics = Topic.objects.filter(owner=request.user).order_by('date_added')

    context = {'topics': topics}

    return render(request, 'learning_logs/topics.html', context)

--snip--

 

 

 

 

 

 

 

 

使用者登錄後,request 物件將有一個user 屬性,這個屬性存儲了有關該使用者的資訊。代碼Topic.objects.filter(owner=request.user) Django只從資料庫中獲取owner 屬性為當前使用者的Topic 物件。由於我們沒有修改主題的顯示方式,因此無需對頁面topics的範本做任何修改。

要查看結果,以所有既有主題關聯到的使用者的身份登錄,並訪問topics頁面,你將看到所有的主題。然後,註銷並以另一個用戶的身份登錄,topics頁面將不會列出任何主題。

19.3.4 保護使用者的主題

我們還沒有限制對顯示單個主題的頁面的訪問,因此任何已登錄的用戶都可輸入類似於http://localhost:8000/topics/1/URL,來訪問顯示相應主題的頁面。

你自己試一試就明白了。以擁有所有主題的使用者的身份登錄,訪問特定的主題,並複製該頁面的URL,或將其中的ID記錄下來。然後,註銷並以另一個用戶的身份登錄,再輸入顯示前述主題的頁面的URL。雖然你是以另一個用戶登錄的,但依然能夠查看該主題中的條目。

為修復這種問題,我們在視圖函數topic() 獲取請求的條目前執行檢查:

views.py

 

  from django.shortcuts import render

from django.http import HttpResponseRedirect, Http404

  from django.core.urlresolvers import reverse

  --snip--

 

  @login_required

  def topic(request, topic_id):

      """顯示單個主題及其所有的條目"""

      topic = Topic.objects.get(id=topic_id)

      # 確認請求的主題屬於當前使用者

     if topic.owner != request.user:

          raise Http404

 

      entries = topic.entry_set.order_by('-date_added')

      context = {'topic': topic, 'entries': entries}

      return render(request, 'learning_logs/topic.html', context)

  --snip--

 

 

 

 

 

 

 

 

伺服器上沒有請求的資源時,標準的做法是返回404回應。在這裡,我們導入了異常Http404 (見),並在用戶請求它不能查看的主題時引發這個異常。收到主題請求後,我們在渲染網頁前檢查該主題是否屬於當前登錄的用戶。如果請求的主題不歸當前使用者所有,我們就引發Http404 異常(見),Django返回一個404錯誤頁面。

現在,如果你試圖查看其他使用者的主題條目,將看到Django發送的消息Page Not Found。在第20章,我們將對這個專案進行配置,讓使用者看到更合適的錯誤頁面。

19.3.5 保護頁面edit_entry

頁面edit_entry URLhttp://localhost:8000/edit_entry/entry_id / ,其中 entry_id 是一個數字。下面來保護這個頁面,禁止使用者通過輸入類似於前面的URL來訪問其他用戶的條目:

views.py

 

--snip--

@login_required

def edit_entry(request, entry_id):

    """編輯既有條目"""

    entry = Entry.objects.get(id=entry_id)

    topic = entry.topic

    if topic.owner != request.user:

        raise Http404

 

    if request.method != 'POST':

        # 初次請求,使用當前條目的內容填充表單

        --snip--

 

 

 

 

 

 

 

 

我們獲取指定的條目以及與之相關聯的主題,然後檢查主題的所有者是否是當前登錄的用戶,如果不是,就引發Http404 異常。

19.3.6 將新主題關聯到當前使用者

當前,用於添加新主題的頁面存在問題,因此它沒有將新主題關聯到特定使用者。如果你嘗試添加新主題,將看到錯誤消息IntegrityError ,指出learning_logs_topic.user_id 不能為NULL Django的意思是說,創建新主題時,你必須指定其owner 欄位的值。

由於我們可以通過request 物件獲悉當前使用者,因此存在一個修復這種問題的簡單方案。請添加下面的代碼,將新主題關聯到當前使用者:

views.py

 

  --snip--

  @login_required

  def new_topic(request):

      """添加新主題"""

      if request.method != 'POST':

          # 沒有提交的資料,創建一個空表單

          form = TopicForm()

      else:

          # POST提交的資料,對資料進行處理

          form = TopicForm(request.POST)

          if form.is_valid():

             new_topic = form.save(commit=False)

             new_topic.owner = request.user

             new_topic.save()

              return HttpResponseRedirect(reverse('learning_logs:topics'))

 

      context = {'form': form}

      return render(request, 'learning_logs/new_topic.html', context)

   --snip--

 

 

 

 

 

 

 

 

我們首先調用form.save() ,並傳遞實參commit=False ,這是因為我們先修改新主題,再將其保存到資料庫中(見)。接下來,將新主題的owner 屬性設置為當前使用者(見)。最後,對剛定義的主題實例調用save() (見)。現在主題包含所有必不可少的資料,將被成功地保存。

現在,這個項目允許任何用戶註冊,而每個使用者想添加多少新主題都可以。每個使用者都只能訪問自己的資料,無論是查看資料、輸入新資料還是修改舊資料時都如此。

動手試一試

19-3 重構 :在views.py中,我們在兩個地方核實主題關聯到的使用者為當前登錄的用戶。請將執行這種檢查的代碼放在一個名為check_topic_owner() 的函數中,並在恰當的地方調用這個函數。

19-4 保護頁面new_entry :一個使用者可在另一個使用者的學習筆記中添加條目,方法是輸入這樣的URL,即其中包含輸入另一個使用者的主題的ID。為防範這種攻擊,請在保存新條目前,核實它所屬的主題歸當前使用者所有。

19-5 受保護的博客 :在你創建的專案Blog中,確保每篇博文都與特定用戶相關聯。確保任何用戶都可訪問所有的博文,但只有已登錄的用戶能夠發表博文以及編輯既有博文。在讓用戶能夠編輯其博文的視圖中,在處理表單前確認用戶編輯的是他自己發表的博文。

19.4 小結

在本章中,你學習了如何使用表單來讓使用者添加新主題、添加新條目和編輯既有條目。接下來,你學習了如何實現用戶帳戶。你讓老用戶能夠登錄和註銷,並學習了如何使用Django提供的表單UserCreationForm 讓用戶能夠創建新帳戶。

建立簡單的使用者身份驗證和註冊系統後,你通過使用裝飾器@login_required 禁止未登錄的使用者訪問特定頁面。然後,你通過使用外鍵將資料關聯到特定使用者,還學習了如何執行要求指定預設資料的資料庫遷移。

最後,你學習了如何修改視圖函數,讓使用者只能看到屬於他的資料。你使用方法filter() 來獲取合適的資料,並學習了如何將請求的資料的所有者同當前登錄的用戶進行比較。

該讓哪些資料可隨便訪問,該對哪些資料進行保護呢?這可能並非總是那麼顯而易見,但通過不斷地練習就能掌握這種技能。在本章中,我們就該如何保護使用者資料所做的決策表明,與人合作開發項目是個不錯的主意:有人對項目進行檢查的話,更容易發現其薄弱環節。

至此,我們創建了一個功能齊備的項目,它運行在本地電腦上。在本書的最後一章,我們將設置這個專案的樣式,使其更漂亮;我們還將把它部署到一台伺服器上,讓任何人都可通過互聯網註冊並創建帳戶。 

0 留言:

發佈留言