咱們將創建一個用戶註冊和身份驗證系統,讓用戶可以註冊帳戶,進而登陸和註銷。咱們將建立一個新的應用程序,其中包含與處理用戶帳戶相關的全部功能。咱們還將對模型Topic 稍作修改,讓每一個主題都歸屬於特定用戶。html
咱們首先使用命令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
在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', ]
接下來,咱們須要修改項目根目錄中的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 %}
咱們要讓登陸視圖處理表單,所以將實參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)
(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 ,讓每一個主題都歸屬於特定用戶。這也將影響條目,由於每一個條目都屬於特定的主題。咱們先來限制對一些頁面的訪問。
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編程,從入門到實踐