Django web編程3 -- 建立用戶帳戶

咱們將創建一個用戶註冊和身份驗證系統,讓用戶可以註冊帳戶,進而登陸和註銷。咱們將建立一個新的應用程序,其中包含與處理用戶帳戶相關的全部功能。咱們還將對模型Topic 稍作修改,讓每一個主題都歸屬於特定用戶。html

一、應用程序users

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

(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 相同。sql

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

在settings.py中,咱們須要將這個新的應用程序添加到INSTALLED_APPS 中,以下所示:shell

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

    # 個人應用程序
    'learning_logs',
    'users',
]

1.2 包含應用程序users 的URL

接下來,咱們須要修改項目根目錄中的urls.py,使其包含咱們將爲應用程序users 定義的URL:數據庫

from django.contrib import admin
from django.conf.urls import url, include

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'', include('learning_logs.urls',namespace='learning_logs')),
    url(r'^users/', include('users.urls', namespace='users')),
]

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

二、登錄頁面

咱們首先來實現登陸頁面的功能。爲此,咱們將使用Django提供的默認登陸視圖,所以URL模式會稍有不一樣。在目錄learning_log/users/中,新建一個名爲urls.py的文件,並在其中添加以下代碼:編程

from django.conf.urls import url
from django.contrib.auth.views import LoginView

from . import views
app_name='users'

urlpatterns = [
    # 登陸頁面
    url(r'^login/$', LoginView.as_view(template_name='users/login.html'),
       name='login'),

]

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

(1)模板login.htmlapp

用戶請求登陸頁面時,Django將使用其默認視圖login ,但咱們依然須要爲這個頁面提供模板。爲此,在目錄learning_log/users/中,建立一個名爲templates的目錄,並在其中建立一個名爲users的目錄。如下是模板login.html,你應將其存儲到目錄learning_log/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在用戶成功登陸後將其重定向到什麼地方——在這裏是主頁。

(2)連接到登錄頁面

下面在learning_logs/templates/learning_logs/base.html中添加到登陸頁面的連接,讓全部頁面都包含它。用戶已登陸時,咱們不想顯示這個連接,所以將它嵌套在一個{% if %}標籤中:

<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。。在這個主頁的頁眉中,顯示了一條 個性化問候語,其中包含你的用戶名。

 三、註銷

 如今須要提供一個讓用戶註銷的途徑。咱們不建立用於註銷的頁面,而讓用戶只需單擊一個連接就能註銷並返回到主頁。爲此,咱們將爲註銷連接定義一個URL模式,編寫一個 視圖函數,並在base.html中添加一個註銷連接。

(1)註銷url

下面的代碼爲註銷定義了URL模式,該模式與URL http://locallwst:8000/users/logout/匹配。修改後的users/urls.py以下:

from django.conf.urls import url
from django.contrib.auth.views import LoginView

from . import views
app_name='users'

urlpatterns = [
    # 登陸頁面
    url(r'^login/$', LoginView.as_view(template_name='users/login.html'),
       name='login'),

    # 註銷
    url(r'^logout/$', views.logout_view, name='logout'),
]

這個URL模式將請求發送給函數logout_view() 。這樣給這個函數命名,旨在將其與咱們將在其中調用的函數logout() 區分開來。

2. 視圖函數logout_view()

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

from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls 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)連接到註銷頁面

如今咱們須要添加一個註銷連接。咱們在learning_logs/templates/learning_logs/base.html 中添加這種連接,讓每一個頁面都包含它;咱們將它放在標籤{% if user.is_authenticated %}中,使得僅當用戶登陸後 才能看到它:

<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 }}.
      <a href="{% url 'users:logout' %}">log out</a>
    {% else %}
        <a href="{% url 'users:login' %}">log in</a>
    {% endif %}
</p>

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

下圖顯示了用戶登陸後看到的主頁。這裏的重點是建立可以正確工做的網站,所以幾乎沒有設置任何樣式。肯定所需的功能都能正確運行後,咱們將設置這個網站的樣式,使 其看起來更專業。

四、註冊頁面

下面來建立一個讓新用戶可以註冊的頁面。咱們將使用Django提供的表單UserCreationForm,但編寫本身的視圖函數和模板。

(1)註冊頁面的URL模式

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

from django.conf.urls import url
from django.contrib.auth.views import LoginView

from . import views
app_name='users'

urlpatterns = [
    # 登陸頁面
    url(r'^login/$', LoginView.as_view(template_name='users/login.html'),
       name='login'),

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

    # 註冊頁面
    url(r'^register/$', views.register, name='register'),
]

(2). 視圖函數register()

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

from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.contrib.auth import login, logout, authenticate
from django.contrib.auth.forms import UserCreationForm

def logout_view(request):
    """註銷用戶"""
    logout(request)
    return HttpResponseRedirect(reverse('learning_logs:index'))

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所在的目錄中:

{% 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)連接到註冊頁面

咱們在learning_logs/templates/learning_logs/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 }}.
      <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 %}
</p>

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

如今,已登陸的用戶看到的是個性化的問候語和註銷連接,而未登陸的用戶看到的是註冊連接和登陸連接。請嘗試使用註冊頁面建立幾個用戶名各不相同的用戶帳戶。
注意  這裏的註冊系統容許用戶建立任意數量的帳戶。有些系統要求用戶確認其身份:發送一封確認郵件,用戶回覆後其帳戶才生效。經過這樣作,系統生成的垃圾帳戶將比這裏使用的簡單系統少。然而,學習建立應用程序時,徹底能夠像這裏所作的那樣,使用簡單的用戶註冊系統。

 五、讓用戶擁有本身的數據

 用戶應該可以輸入其專有的數據,所以咱們將建立一個系統,肯定各項數據所屬的用戶,再限制對頁面的訪問,讓用戶只能使用本身的數據。
在本節中,咱們將修改模型Topic ,讓每一個主題都歸屬於特定用戶。這也將影響條目,由於每一個條目都屬於特定的主題。咱們先來限制對一些頁面的訪問。

 5.一、使用@login_required 限制訪問

Django提供了裝飾器@login_required ,讓你可以輕鬆地實現這樣的目標:對於某些頁面,只容許已登陸的用戶訪問它們。裝飾器 (decorator)是放在函數定義前面的指 令,Python在函數運行前,根據它來修改函數代碼的行爲。下面來看一個示例。
1. 限制對topics 頁面的訪問
每一個主題都歸特定用戶全部,所以應只容許已登陸的用戶請求topics 頁面。爲此,在learning_logs/views.py中添加以下代碼: views.py

 

from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.contrib.auth.decorators import login_required

from .models import Topic, Entry
from .forms import TopicForm,EntryForm

def index(request):
    """學習筆記的主頁"""
    return render(request, 'learning_logs/index.html')

@login_required def topics(request):
    """顯示全部的主題"""
    topics = Topic.objects.order_by('date_added')
    context = {'topics': topics}
    return render(request, 'learning_logs/topics.html', context)

def topic(request, topic_id):
    """顯示特定主題的詳細頁面"""
    topic = Topic.objects.get(id=topic_id)
    entries = topic.entry_set.order_by('-date_added')
    context = {'topic': topic, 'entries': entries}
    return render(request, 'learning_logs/topic.html', context)

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)

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)

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)

咱們首先導入了函數login_required() 。咱們將login_required() 做爲裝飾器用於視圖函數topics() ——在它前面加上符號@ 和login_required ,讓Python在運 行topics() 的代碼前先運行login_required() 的代碼。
login_required() 的代碼檢查用戶是否已登陸,僅當用戶已登陸時,Django才運行topics() 的代碼。若是用戶未登陸,就重定向到登陸頁面。 爲實現這種重定向,咱們須要修改settings.py,讓Django知道到哪裏去查找登陸頁面。請在項目learning_log的Django設置settings.py末尾添加以下代碼:

# 個人設置
LOGIN_URL = '/users/login/'

如今,若是未登陸的用戶請求裝飾器@login_required 的保護頁面,Django將重定向到settings.py中的LOGIN_URL 指定的URL。
要測試這個設置,可註銷並進入主頁。而後,單擊連接Topics,這將重定向到登陸頁面。接下來,使用你的帳戶登陸,並再次單擊主頁中的Topics連接,你將看到topics頁面。

二、全面限制對項目「學習筆記」的訪問

Django讓你可以輕鬆地限制對頁面的訪問,但你必須針對要保護哪些頁面作出決定。最好先肯定項目的哪些頁面不須要保護,再限制對其餘全部頁面的訪問。你能夠輕鬆地修改 過於嚴格的訪問限制,其風險比不限制對敏感頁面的訪問更低。
在項目「學習筆記」中,咱們將不限制對主頁、註冊頁面和註銷頁面的訪問,並限制對其餘全部頁面的訪問。 在下面的learning_logs/views.py中,對除index() 外的每一個視圖都應用了裝飾器@login_required :

--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,都應限制對它們的訪問。

三、將數據關聯到用戶

如今,須要將數據關聯到提交它們的用戶。咱們只需將最高層的數據關聯到用戶,這樣更低層的數據將自動關聯到用戶。例如,在項目「學習筆記」中,應用程序的最高層數據是
主題,而全部條目都與特定主題相關聯。只要每一個主題都歸屬於特定用戶,咱們就能肯定數據庫中每一個條目的全部者。
下面來修改模型Topic ,在其中添加一個關聯到用戶的外鍵。這樣作後,咱們必須對數據庫進行遷移。最後,咱們必須對有些視圖進行修改,使其只顯示與當前登陸的用戶相關 聯的數據。

(1)修改模型Topic

對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會話,並執行以下命令:

(ll_env) [root@xxjt learning_log]# python3 manage.py shell>>> from django.contrib.auth.models import User
>>> User.objects.all()
<QuerySet [<User: happy>, <User: test1234>]>
>>> 
>>> for user in User.objects.all():
...     print(user.username, user.id)
... 
happy 1
test1234 2

咱們在shell會話中導入了模型User 。而後,咱們查看到目前爲止都建立了哪些用戶。 咱們遍歷用戶列表,並打印每位用戶的用戶名和ID。Django詢問要將既有主題關聯到哪一個用戶時,咱們將指定其中的一個ID值。

3. 遷移數據庫

知道用戶ID後,就能夠遷移數據庫了。

 

 

參考資料:

一、python編程,從入門到實踐

相關文章
相關標籤/搜索