Web應用程序的核心是讓任何用戶都可以註冊帳戶並可以使用它,無論用戶身處何方html
創建用於建立用戶的身份驗證系統以前,咱們先來添加幾個頁面,讓用戶可以輸入數據。當前,只有超級用戶可以經過管理網站輸入數據。咱們不想讓用戶與管理網站交互,所以咱們將使用Django的表單建立工具來建立讓用戶可以輸入數據的頁面python
首先讓用戶可以添加新主題。建立基於表單的頁面的方法幾乎與前面建立網頁同樣:定義一個URL,編寫一個視圖函數並編寫一個模板。主要差異是,須要導入包含表單的模塊forms.pyshell
用於添加主題的表單數據庫
在Django中,建立表單最簡單的方法是使用ModelForm,它根據咱們定義的模板中的信息自動建立表單。
首先,建立一個名爲在應用程序目錄下建立forms.py
咱們首先導入了模板forms以及要使用的模板Topic,而後定義了一個名爲TopicForm的類,繼承forms.ModelForm。最簡單的ModelForm只包含一個內嵌的Meta類,它告訴Django根據哪一個模型建立表單,以及在表單中包含哪些字段。上例中,咱們根據Topic建立一個表單,該表單包含text字段,且不用爲text字段生成標籤django
URL模式new_topic瀏覽器
當用戶要添加新主題時,咱們將切換到http://localhost:8000/new_topic/ ,咱們在learning_logs/urls.py中定義網頁new_topic的URL模式
URL模式將請求交給視圖函數new_topic().接下來編寫這個函數服務器
視圖函數new_topic()app
函數new_topic()須要處理兩種情形:剛進入new_topic網頁(在這種狀況下,他應該顯示一個空表單);對提交的表單數據進行處理,並將用戶重定向到網頁topics
首先咱們要導入HttpResponseRedirect類,用戶提交主題後,使用這個類將用戶重定向到網頁topics。函數reverse()根據指定的URL模型肯定URL,這意味着Django將在頁面被請求時生成URL。咱們還導入來了剛纔建立的表單TopicForm函數
# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.shortcuts import render from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse from .models import Topic from .forms import TopicForm # Create your views here. --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)
GET請求和POST請求工具
對於只是從服務器讀取數據的頁面,使用GET請求;在用戶須要經過表單提交信息時,一般使用POST請求
函數new_topic()將請求對象做爲參數。用戶首次請求網頁時,瀏覽器發送GET請求,當用戶填寫並提交表單時,瀏覽器將發送POST請求。根據請求類型能夠肯定用戶請求是空表單(GET請求)仍是對填寫好的表單進行處理(POST請求)
若是請求方法不是POST,請求就多是GET,咱們返回一個空表單。若是請求方法是POST,執行else代碼塊,對提交的表單數據進行處理。咱們根據用戶輸入(它們存儲在request.POST中)建立一個TopicForm實例,這樣form包含了用戶提交的信息
要將提交的信息保存到數據庫,必須先經過檢查它們是否是有效的。函數is_valid()合適用戶填寫了全部必不可少的字段(表單字段默認都是必不可少的),且輸入的數據與要求的字段類型一致(例如,字段text少於200個字符,這是咱們以前在models.py中指定的)。這種自動驗證避免了咱們去作大量的工做。若是字段都有效,咱們調用save()將表單的數據寫入數據庫。保存數據後,就能夠離開這個頁面了。咱們使用reverse()獲取頁面topics的URL,並將其傳遞給HttpResponseRedirect(),將用戶的瀏覽器重定向到頁面topics
模板new_topic
下面來建立新模板new_topic.html,用於顯示咱們剛建立的表單
這個模板繼承了base.html,咱們定義了一個HTML表單,action告訴服務器將提交的表單數據發送到哪裏,這裏咱們將它發回給new_topic(),method讓瀏覽器以POST請求的方式提交數據
Django使用模板標籤{% csrf_token %}
來防止攻擊者利用表單類獲取對服務器未經受權的訪問(這種攻擊被稱爲跨站請求僞造)。咱們用模板變量{{ form.as_p }}
讓Django自動建立顯示錶單所需的所有字段。修飾符as_p讓Django以段落格式渲染全部表單元素,這是一種整潔地顯示錶單的簡單方法
Django不會爲表單建立提交按鈕,所以咱們還定義了一個按鈕
連接到頁面new_topic
接下來,咱們在頁面topics中添加一個到頁面new_topic的連接:
{% 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 %}
連接到new_topic頁面後,能夠利用這個表單添加幾個新主題
如今用戶能夠添加新主題了,但他們還想添加新條目。
用於添加新條目的表單
# -*- coding:utf-8 -*- from django import forms from .models import Topic, Entry --snip-- class EntryForm(forms.ModelForm): class Meta: model = Entry fields = ['text'] labels = {'text': ''} widgets = {'text': forms.Textarea(attrs={'cols': 80})}
咱們導入Entry。新類EntryForm繼承forms.ModelForm,它包含的Meta類指出了表單基於的模型以及要在表單中包含哪些字段。這裏也給text指定了一個空標籤
另外,咱們定義了屬性widgets。小部件(widget)是一個HTML表單元素,如單行文本框、多行文本框或下拉列表。經過設置屬性widgets,能夠覆蓋Django選擇的默認小部件。經過讓Django使用forms.Textarea,咱們定製了字段’text'的輸入 小部件,將文本區域的寬度設置爲80列。這給用戶提供了足夠的空間,能夠編寫有意義的條目
URL模式new_entry
--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中。當請求匹配這個模式時,Django將請求和主題ID發送給函數new_entry()
視圖函數new_entry()
視圖new_entry與視圖new_topic很像
# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.shortcuts import render from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse from .models import Topic from .forms import TopicForm, EntryForm # Create your views here. --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)
咱們導入了剛建立的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()時,提供了兩個參數:根據它來生成URL的URL模式的名稱;列表args,其中包含要包含在URL中的全部實參。這裏,列表args只有topic_id一個元素
模板new_entry
從下面的代碼可知,模板new_entry相似於模板new_entry
{% 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_entry
接下來,咱們須要在顯示特定主題的頁面中添加到new_entry的連接:
{% 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 %}
嘗試添加新條目
下面建立一個頁面,讓用戶可以編輯既有的條目
URL模式edit_entry
# -*- coding:utf-8 -*- """定義learning_logs的URL模式""" from django.conf.urls import url from . import views urlpatterns = [ --snip-- # 用於編輯條目的頁面 url(r'^edit_entry/(?P<entry_id>\d+)/$', views.edit_entry, name='edit_entry'), ]
該模式匹配如:http://localhost:8000/edit_entry/1/ 的請求,發送給視圖函數edit_entry()
視圖函數edit_entry()
頁面edit_entry收到GET請求,edit_entry()返回一個表單,讓用戶可以對條目進行編輯。收到POST請求,將修改後的文本保存到數據庫中
# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.shortcuts import render from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse from .models import Topic, Entry from .forms import TopicForm, EntryForm # Create your views here. --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(),接下來咱們重定向到顯示條目所屬主題的頁面,用戶將在其中看到其編輯的條目的新版本
模板edit_entry
下面是模板edit_entry.html,它與模板new_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 %}
連接到頁面edit_entry
如今在特定的主題頁面中,須要給每一個條目添加到頁面edit_entry的連接:
--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模式edit_entry和當前條目的id屬性entry.id來肯定URL
接下來,咱們就將創建一個用戶註冊和身份驗證的系統,讓用戶可以註冊帳戶,進而登陸和註銷。咱們將新建一個應用程序,其中包含與處理用戶帳戶相關的全部功能,咱們還將對模型Topic稍作修改,讓每一個主題都歸屬於特定用戶
咱們首先使用startapp
命令來建立一個名爲users的應用程序:
將應用程序users添加到setting.py中
包含應用程序users的URL
首先來實現登陸頁面的功能,爲此咱們將使用Django提供的默認的登陸視圖,所以須要對URL模式稍做修改。咱們在應用程序users目錄中,新建一個 urls.py文件,添加以下代碼:
咱們導入了默認視圖login,匹配了該URL模式的請求將發送給Django默認視圖login,注意:這裏的視圖實參是login,不是views.login。鑑於咱們沒有編寫本身的視圖函數,咱們傳遞了一個字典,告訴Django去哪裏查找咱們將編寫的模板
模板login.html
用於請求的登陸頁面時,Django將使用其默認視圖login,但咱們依然須要爲這個頁面提供模板。爲此,在users中建立一個名爲templates的目錄,在其中建立一個名爲users的目錄。如下是模板login.html,它應該在users/templates/users/中
{% 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在用戶成功登陸後將其重定向到什麼地方
連接到登陸頁面
下面在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屬性:若是用戶已登陸,該屬性爲Ture,不然爲Fals,咱們經過該屬性判斷用戶是否登陸,顯示不一樣消息
使用登陸頁面
前面創建一個用戶帳戶,下面來登陸一下,看看登陸頁面是否管用。請訪問http://localhost:8000/admin/ ,若是依然是以管理員的身份登陸的,先註銷,註銷後,訪問http://localhost:8000/users/login/ ,你將看到以下界面,輸入用戶名密碼,將進入頁面index,在這個頁面中,顯示一條問候語,其中包含你的用戶名
如今須要提供一個讓用戶註銷的途徑。咱們不建立用於註銷的頁面,而讓用戶只需點擊一個連接就能註銷並返回到主頁。爲此咱們爲註銷定義一個URL模式,編寫一個視圖函數,並在base.html中添加一個註銷連接
註銷URL
--snip-- urlpatterns = [ # 登錄頁面 --snip-- # 註銷 url(r'^logout/$', views.logout_view, name='logout') ]
視圖函數logout_view()
函數logout_view()很簡單:咱們從django.contrib.auth中導入函數logout(),並調用它,再重定向到主頁。咱們再users/views.py中輸入下面的代碼
# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.shortcuts import render from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse from django.contrib.auth import logout # Create your views here. def logout_view(request): """註銷用戶""" logout(request) return HttpResponseRedirect(reverse('learning_logs:index'))
連接到註銷視圖
如今咱們須要添加一個註銷連接,咱們在base.html中添加這種連接,讓每一個頁面都包含它:咱們將它放在標籤{% if user.is_authenticated %}
中,使得僅當用戶登陸後才能看到它:
--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 %} </p> --snip--
而後咱們登陸就看到的是下面的樣子:
下面來建立一個新用戶可以註冊的頁面。 咱們將使用Django提供的表單UserCreationForm,但編寫本身的視圖函數和模板
註冊頁面的URL模式
--snip-- urlpatterns = [ # 登錄頁面 --snip-- # 註冊頁面 url(r'^register/$', views.register, name='register'), ]
這個模式與URL http://lcalhost:8000/users/register/ 匹配,將請求發送給咱們編寫的函數register()
視圖函數register()
# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.shortcuts import render from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse from django.contrib.auth import logout, login, 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中。
保存用戶的信息後,咱們讓用戶自動登陸,這包含兩個步驟:
註冊模板
註冊頁面的模板與登陸頁面的模板相似,它跟login.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在表單中正確地顯示全部的字段,包括錯誤消息——若是用戶沒有正確地填寫表單
連接到註冊頁面
接下來咱們,添加以下代碼,即用戶沒有登陸時顯示到註冊頁面的連接:
--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--
如今,已登陸的用戶看到的是:
而未登陸的用戶看到的是:
用戶應該能輸入其專有的數據,所以咱們將建立一個系統,肯定各項數據所屬的用戶,再限制對頁面的訪問,讓用戶只能使用本身的數據
咱們將修改模型Topic,讓每一個主題都歸屬於特定用戶。這也將影響條目,由於每一個條目都屬於特定的主題。咱們先來限制對一些頁面的訪問
Django提供了裝飾器@login_required,讓你可以輕鬆地實現這樣的目標:對於某些頁面,只容許已登陸的用戶訪問它們。裝飾器(decorator)是放在函數定義前面的,Python在函數運行前,根據它來修改函數代碼的行爲
限制對topics頁面的訪問
每一個主題都歸特定的用戶全部,所以應只容許已登陸的用戶請求topics頁面。爲此咱們修改learning_logs/views.py的代碼
咱們導入了函數login_required()。在視圖函數topics()前加上符號@和login_required,讓Python在運行topics()代碼的前先運行login_required()的代碼。login_required()的代碼檢查用戶是否已經登陸,僅當用戶已登陸時,Django才運行topics()的代碼。若是用戶未登陸,就重定向到登陸頁面
爲實現這種重定向,咱們修改settings.py,繞過Django知道到哪裏去查找登陸頁面
如今,未登陸的用戶請求修飾器@login_required的保護頁面,Django將重定向到settings.py中的LOGIN_URL指定的URL
要測試這個設置,可註銷進入主頁,單擊連接Topics,將重定向到登陸頁面。使用你的帳號登陸後,再次點擊Topics連接,你將看到topics頁面
全面限制對項目「學習筆記」的訪問
Django讓你可以輕鬆地限制對頁面的訪問,但你必須針對要保護哪些頁面作出決定。最好先肯定項目的哪些頁面不須要保護,再限制對其餘全部頁面的訪問。你能夠輕鬆地修改過於嚴格的訪問限制,其風險比不限制對敏感頁面地訪問更低
在「學習筆記」項目中,咱們不限制對主頁、註冊頁面和註銷頁面的訪問,並限制對其餘全部頁面的訪問
--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--
未登陸的狀況下嘗試訪問這些頁面,都將重定向到登陸頁面
如今,須要將數據關聯到提交它們的用戶。咱們只需將最高層的數據關聯到用戶,這樣更低層的數據將自動關聯到用戶。例如,在項目「學習筆記」中,應用程序的最高數據是主題,全部條目都與特定主題相關聯。只要每一個主題都歸屬於特定用戶,咱們就能肯定數據庫中每一個條目的全部者
修改模型Topic
對models.py的修改只涉及兩行代碼
# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import models from django.contrib.auth.models import User # Create your models here. class Topic(models.Model): """用戶學習的主題""" text = models.CharField(max_length=200) date_added = models.DateTimeField(auto_now_add=True) owner = models.ForeignKey(User) def __unicode__(self): """返回模型的字符串表示""" return self.text --snip--
咱們導入了django.contrib.auth中的模型User,而後再Topic中添加了字段owner,它創建到模型User的外鍵關係
肯定當前有哪些用戶
在咱們肯定將各個既有主題關聯到哪一個用戶上時,須要先知道關聯用戶的ID,爲此咱們打開一個Django Shell會話,執行以下命令,查看已建立的全部用戶的ID
shell會話中輸出了三個用戶:hasee_z、eric、willie,並打印了它們的ID,Django詢問要將既有主題關聯到哪一個用戶時,咱們將指定其中的一個ID值
遷移數據庫
知道用戶ID後,就能夠遷移數據庫了:
如今能夠執行遷移了,咱們繼續執行命令python manage.py migrate
Django應用新的遷移,結果一切順利。爲驗證符合預期,咱們在Shell會話中這樣作
當前,無論你以哪一個用戶的身份登陸,都能看到全部的主題。爲只向用戶顯示屬於本身的主題,咱們對views.py中的topics()作以下修改:
--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頁面將不會列出任何主題
咱們還沒限制對顯示單個主題的頁面的訪問,所以任何已登陸的用戶均可以輸入相似於:http://localhost:8000/topics/1/ 的URL,來訪問顯示相應主題的頁面
爲了修復這種問題,咱們在視圖函數topic()獲取請求的條目前執行檢察:
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錯誤頁面
結果以下:
頁面edit_entry的URL爲http://localhost:8000/edit_entry/entry_id/ ,其中entry_id是一個數字。下面來保護這個頁面,禁止用戶經過輸入相似前面的URL來訪問其餘用戶的條目:
--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異常
當前,咱們添加新主題的頁面存在問題,沒有將新主題關聯到特定用戶。嘗試添加新主題,看到錯誤IntegrityError,指出learning_logs_topic.user_id不能爲NULL,即建立新主題時,必須指定其owner字段的值
因而咱們修改views.py中new_topic()的代碼:
--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()。如今新主題包含全部必不可少的數據,將被成功地保存。 如今,這個項目容許任何用戶註冊,並且用戶想添加多少新主題均可以。每一個用戶都只能訪問本身的數據,不管是查看數據、輸入新數據仍是修改舊數據時都如此。