用戶帳戶——《Python編程從入門到實踐》

Web應用程序的核心是讓任何用戶都可以註冊帳戶並可以使用它,無論用戶身處何方html

一、讓用戶可以輸入數據

創建用於建立用戶的身份驗證系統以前,咱們先來添加幾個頁面,讓用戶可以輸入數據。當前,只有超級用戶可以經過管理網站輸入數據。咱們不想讓用戶與管理網站交互,所以咱們將使用Django的表單建立工具來建立讓用戶可以輸入數據的頁面python

1.添加新主題:

首先讓用戶可以添加新主題。建立基於表單的頁面的方法幾乎與前面建立網頁同樣:定義一個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頁面後,能夠利用這個表單添加幾個新主題

2.添加新條目:

如今用戶能夠添加新主題了,但他們還想添加新條目。

用於添加新條目的表單

# -*- 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 %}

嘗試添加新條目

3.編輯條目:

下面建立一個頁面,讓用戶可以編輯既有的條目

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稍作修改,讓每一個主題都歸屬於特定用戶

1.應用程序users:

咱們首先使用startapp命令來建立一個名爲users的應用程序:

將應用程序users添加到setting.py中

包含應用程序users的URL

2.登陸頁面:

首先來實現登陸頁面的功能,爲此咱們將使用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,在這個頁面中,顯示一條問候語,其中包含你的用戶名


3.註銷:

如今須要提供一個讓用戶註銷的途徑。咱們不建立用於註銷的頁面,而讓用戶只需點擊一個連接就能註銷並返回到主頁。爲此咱們爲註銷定義一個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--

而後咱們登陸就看到的是下面的樣子:

4.註冊頁面

下面來建立一個新用戶可以註冊的頁面。 咱們將使用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中。
保存用戶的信息後,咱們讓用戶自動登陸,這包含兩個步驟:

  • 首先咱們,調用authenticate(),並將實參new_user.username和密碼傳遞給它。用戶註冊時,被要求輸入密碼兩次;因爲表單是有效的,咱們知道輸入的這兩個密碼是相同的,所以可使用其中任何一個。在這裏,咱們從表單的POST數據中獲取與鍵’password1'相關聯的值。若是用戶名和密碼無誤,方法authenticate()將返回一個經過了身份驗證的用戶對象,咱們將其存儲在authenticated_user中
  • 接下來,咱們調用login(),將對象request和authenticated_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,讓每一個主題都歸屬於特定用戶。這也將影響條目,由於每一個條目都屬於特定的主題。咱們先來限制對一些頁面的訪問

1.使用@login_required限制訪問

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--

未登陸的狀況下嘗試訪問這些頁面,都將重定向到登陸頁面

2.將數據關聯到用戶

如今,須要將數據關聯到提交它們的用戶。咱們只需將最高層的數據關聯到用戶,這樣更低層的數據將自動關聯到用戶。例如,在項目「學習筆記」中,應用程序的最高數據是主題,全部條目都與特定主題相關聯。只要每一個主題都歸屬於特定用戶,咱們就能肯定數據庫中每一個條目的全部者

修改模型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會話中這樣作

3.只容許用戶訪問本身的主題

當前,無論你以哪一個用戶的身份登陸,都能看到全部的主題。爲只向用戶顯示屬於本身的主題,咱們對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頁面將不會列出任何主題

4.保護用戶的主題

咱們還沒限制對顯示單個主題的頁面的訪問,所以任何已登陸的用戶均可以輸入相似於: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錯誤頁面
結果以下:

5.保護頁面edit_entry

頁面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異常

6.將新主題關聯到當前用戶

當前,咱們添加新主題的頁面存在問題,沒有將新主題關聯到特定用戶。嘗試添加新主題,看到錯誤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()。如今新主題包含全部必不可少的數據,將被成功地保存。 如今,這個項目容許任何用戶註冊,並且用戶想添加多少新主題均可以。每一個用戶都只能訪問本身的數據,不管是查看數據、輸入新數據仍是修改舊數據時都如此。

相關文章
相關標籤/搜索