Django web編程3 -- 創建用戶賬戶


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

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.1 將應用程序users 添加到settings.py中

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

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區分開來。

2、登陸頁面

我們首先來實現登錄頁面的功能。為此,我們將使用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 中。

(1)模板login.html

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

 3、注銷

 現在需要提供一個讓用戶注銷的途徑。我們不創建用於注銷的頁面,而讓用戶只需單擊一個鏈接就能注銷並返回到主頁。為此,我們將為注銷鏈接定義一個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 %}

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

4、注冊頁面

下面來創建一個讓新用戶能夠注冊的頁面。我們將使用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 %}

現在,已登錄的用戶看到的是個性化的問候語和注銷鏈接,而未登錄的用戶看到的是注冊鏈接和登錄鏈接。請嘗試使用注冊頁面創建幾個用戶名各不相同的用戶賬戶。
注意  這里的注冊系統允許用戶創建任意數量的賬戶。有些系統要求用戶確認其身份:發送一封確認郵件,用戶回復后其賬戶才生效。通過這樣做,系統生成的垃圾賬戶將比這里使用的簡單系統少。然而,學習創建應用程序時,完全可以像這里所做的那樣,使用簡單的用戶注冊系統。

 5、讓用戶擁有自己的數據

 用戶應該能夠輸入其專有的數據,因此我們將創建一個系統,確定各項數據所屬的用戶,再限制對頁面的訪問,讓用戶只能使用自己的數據。
在本節中,我們將修改模型Topic ,讓每個主題都歸屬於特定用戶。這也將影響條目,因為每個條目都屬於特定的主題。我們先來限制對一些頁面的訪問。

 5.1、使用@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頁面。

2、全面限制對項目“學習筆記”的訪問

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

3、將數據關聯到用戶

現在,需要將數據關聯到提交它們的用戶。我們只需將最高層的數據關聯到用戶,這樣更低層的數據將自動關聯到用戶。例如,在項目“學習筆記”中,應用程序的最高層數據是
主題,而所有條目都與特定主題相關聯。只要每個主題都歸屬於特定用戶,我們就能確定數據庫中每個條目的所有者。
下面來修改模型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后,就可以遷移數據庫了。

 

 

參考資料:

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM