跟書《python編程:從入門到實踐》,學習用Django編寫名爲「學習筆記」的Web應用程序。html
Web應用程序的核心是讓任何用戶都可以註冊帳戶並可以使用它,無論用戶身處何方。咱們能夠建立一些表單,讓用戶可以添加主題和條目,以及編輯既有的條目。python
先來添加幾個頁面,讓用戶可以輸入數據。可讓用戶可以添加新主題、添加新條目以及編輯既有條目,但不能經過管理網站來輸入,由於只有超級用戶才能夠這樣作。sql
首先來讓用戶可以添加新主題。建立基於表單的頁面的方法幾乎與前面建立網頁同樣:定義一個URL,編寫一個視圖函數並編寫一個模板。一個主要差異是,須要導入包含表單的模塊forms.py。shell
在Django中,建立表單的最簡單方式是使用ModelForm。建立一個名爲forms.py的文件,將其存儲到models.py所在的目錄中,並在其中編寫第一個表單。數據庫
# vim learning_logs/forms.py
from django import formsfrom .models import Topicclass TopicForm(forms.ModelForm): class Meta: model = Topic fields = ['text'] labels = {'text': ''}
首先導入了模塊forms以及要使用的模型Topic。而後定義了一個名爲TopicForm的類,它繼承了forms.ModelForm。django
最簡單的ModelForm版本只包含一個內嵌的Meta類,它告訴Django根據哪一個模型建立表單,以及在表單中包含哪些字段。咱們根據模型Topic建立一個表單,該表單只包含字段text,且讓Django不要爲字段text生成標籤。編程
new_topic
:# vim learning_logs/urls.py
"""定義learning_logs的URL模式"""from django.urls import path, re_pathfrom . import views app_name='learning_logs'urlpatterns = [ # 主頁 path('', views.index, name='index'), # 顯示全部的主題 path('topics/', views.topics, name='topics'), # 特定主題的詳細頁面 re_path(r'^topics/(?P\d+)/$', views.topic, name='topic'), # 用於添加新主題的頁面 path('new_topic/', views.new_topic, name='new_topic'),]
這個URL模式會將請求交給視圖函數new_topic()
。vim
new_topic()
:函數new_topic()
須要處理兩種情形:剛進入new_topic
網頁,它應顯示一個空表單;對提交的表單數據進行處理,並將用戶重定向到網頁topics。瀏覽器
# vim learning_logs/views.py
from django.shortcuts import renderfrom django.http import HttpResponseRedirectfrom django.urls import reversefrom .models import Topicfrom .forms import TopicFormdef index(request): """學習筆記的主頁""" return render(request, 'learning_logs/index.html')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)
導入了HttpResponseRedirect
類和剛纔建立的表單TopicForm
,用戶提交主題後咱們將使用HttpResponseRedirect
類將用戶重定向到網頁topics。函數reverse()
根據指定的URL模型肯定URL,這意味着Django將在頁面被請求時生成URL。服務器
首先判斷請求方法是不是POST——若是請求方法不是POST,返回一個空表單;若是請求方法是POST,將使用用戶輸入的數據(存儲在request.POST中)建立一個TopicForm
實例,這樣對象form將包含用戶提交的信息。
而後要將提交的信息保存到數據庫,必須先經過檢查肯定它們是有效的。函數is_valid()
覈實用戶填寫了全部必不可少的字段(表單字段默認都是必不可少的),且輸入的數據與要求的字段類型一致。若是全部字段都有效,咱們就調用save()
將表單中的數據寫入數據庫。
最後使用reverse()
獲取頁面topics的URL,並將其傳遞給HttpResponseRedirect()
,後者將用戶的瀏覽器重定向到頁面topics。在頁面topics中,用戶將在主題列表中看到他剛輸入的主題。
建立Web應用程序時,將用到的兩種主要請求類型是GET請求和POST請求。對於只是從服務器讀取數據的頁面,使用GET請求;在用戶須要經過表單提交信息時,一般使用POST請求。處理全部表單時,咱們都將指定使用POST方法。
函數new_topic()
將請求對象做爲參數。用戶初次請求該網頁時,其瀏覽器將發送GET請求;用戶填寫並提交表單時,其瀏覽器將發送POST請求。根據請求的類型,咱們能夠肯定用戶請求的是空表單(GET請求)仍是要求對填寫好的表單進行處理(POST請求)。
new_topic
:# vim learning_logs/templates/learning_logs/new_topic.html
{% extends "learning_logs/base.html" %} {% block content %} <p>Add a new topic:p> <form action="{% url 'learning_logs:new_topic' %}" method='post'> {% csrf_token %} {{ form.as_p }} <button name="submit">add topicbutton> form> {% endblock content %}
繼承了base.html,而後定義了一個HTML表單。實參action告訴服務器將提交的表單數據發送到哪裏,這裏咱們將它發回給視圖函數
new_topic()
。實參method讓瀏覽器以POST請求的方式提交數據。Django使用模板標籤
{% csrf_token %}
來防止***者利用表單來得到對服務器未經受權的訪問(這種***被稱爲跨站
請求僞造)。接着顯示錶單,只需包含模板變量{{ form.as_p }}
,就可以讓Django自動建立顯示錶單所需的所有字段。修
飾符as_p
讓Django以段落格式渲染全部表單元素。
new_topic
:# vim learning_logs/templates/learning_logs/topics.html
{% extends "learning_logs/base.html" %} {% block content %} <p>Topicsp> <ul> {% for topic in topics %} <li> <a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}a> li> {% empty %} <li>No topics have been added yet.li> {% endfor %} ul> <a href="{% url 'learning_logs:new_topic' %}">Add a new topic:a>{% endblock content %}
如今已經能夠添加新主題了,可是還不能添加新條目,再次定義URL,編寫視圖函數和模板,並連接到添加新條目的網頁。
建立一個與模型Entry相關聯的表單。
# vim learning_logs/forms.py
from django import formsfrom .models import Topic, Entryclass TopicForm(forms.ModelForm): class Meta: model = Topic fields = ['text'] labels = {'text': ''}class EntryForm(forms.ModelForm): class Meta: model = Entry fields = ['text'] labels = {'text': ''} wgdgets = {'text': forms.Textarea(attrs={'cols': 80})}
除了導入Topic外,還須要導入Entry。新類EntryForm繼承了forms.ModelForm,它包含的Meta類指出了表單基於的模型以及要在表單中包含哪些字段。
後面定義了屬性widgets
,小部件(widget)是一個HTML表單元素,如單行文本框、多行文本區域或下拉列表。經過設置屬性widgets
,可覆蓋Django選擇的默認小部件。經過讓Django使用forms.Textarea,咱們定製了字段'text'
的輸入小部件,將文本區域的寬度設置爲80列,而不是默認的40列。
new_entry
:在用於添加新條目的頁面的URL模式中,須要包含實參topic_id ,由於條目必須與特定的主題相關聯。
# vim learning_logs/urls.py
"""定義learning_logs的URL模式"""from django.urls import path, re_pathfrom . import views app_name='learning_logs'urlpatterns = [ # 主頁 path('', views.index, name='index'), # 顯示全部的主題 path('topics/', views.topics, name='topics'), # 特定主題的詳細頁面 re_path(r'^topics/(?P\d+)/$', views.topic, name='topic'), # 用於添加新主題的頁面 path('new_topic/', views.new_topic, name='new_topic'), # 用於添加新條目的頁面 re_path(r'^new_entry/(?P\d+)/$', views.new_entry, name='new_entry'),]
這個URL模式與形式爲http://localhost:8000/new_entry/id/
的URL匹配,其中id是一個與主題ID匹配的數字。代碼(?P
捕獲一個數字值,並將其存儲在變量topic_id
中。請求的URL與這個模式匹配時,Django將請求和主題ID發送給函數new_entry()
。
new_entry()
:視圖函數new_entry()
與視圖函數new_topic()
比較相似。
# vim learning_logs/views.py
from django.shortcuts import renderfrom django.http import HttpResponseRedirectfrom django.urls import reversefrom .models import Topicfrom .forms import TopicForm, EntryFormdef index(request): """學習筆記的主頁""" return render(request, 'learning_logs/index.html')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)
首先導入了剛建立的EntryForm,new_entry()
的定義包含形參topic_id
,用於存儲從URL中得到的值。渲染頁面以及處理表單數據時,都須要知道針對的是哪一個主題,所以咱們使用topic_id
來得到正確的主題。
而後仍是檢查請求方法是POST仍是GET。若是是GET請求,將建立一個空的EntryForm實例。若是請求方法爲POST,咱們就對數據進行處理:建立一個EntryForm實例,使用request對象中的POST數據來填充它;再檢查表單是否有效,若是有效則設置條目對象的屬性topic
,再將條目對象保存到數據庫。
在調用save()
時,傳遞了實參commit=False
,讓Django建立一個新的條目對象,並將其存儲到new_entry
中,但不將它保存到數據庫中。咱們將new_entry
的屬性topic
設置爲在這個函數開頭從數據庫中獲取的主題,而後調用save()
,且不指定任何實參。這將把條目保存到數據庫,並將其與正確的主題相關聯。
最後,將用戶重定向到顯示相關主題的頁面。調用reverse()
時,須要提供兩個實參:要根據它來生成URL的URL模式的名稱;列表args ,其中包含要包含在URL中的全部實參。
new_entry
:模板new_entry
與模板new_topic
也比較相似。
# vim learning_logs/templates/learning_logs/new_entry.html
{% 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 entrybutton> form> {% endblock content %}
首先繼承base.html。接着在頁面頂端顯示了主題,讓用戶知道是在哪一個主題中添加條目;該主題名也是一個連接,可用於返回該主題的主頁面。
表單的實參action 包含URL中的
topic_id
值,讓視圖函數可以將新條目關聯到正確的主題。除此以外,這個模板與模板new_topic.html徹底相同。
new_entry
:# vim learning_logs/templates/learning_logs/topic.html
{% 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 entrya> p> <ul> {% for entry in entries %} <li> <p>{{ entry.date_added|date:'M d, Y H:i' }}p> <p>{{ entry.text|linebreaks }}p> li> {% empty %} <li> There are no entries for this topic yet. li> {% endfor %} ul>{% endblock content %}
在顯示條目前添加連接,由於在這種頁面中,執行的最多見的操做是添加新條目。
下面來建立一個頁面,讓用戶能夠編輯既有的條目。
edit_entry
:# vim learning_logs/urls.py
"""定義learning_logs的URL模式"""from django.urls import path, re_pathfrom . import views app_name='learning_logs'urlpatterns = [ # 主頁 path('', views.index, name='index'), # 顯示全部的主題 path('topics/', views.topics, name='topics'), # 特定主題的詳細頁面 re_path(r'^topics/(?P\d+)/$', views.topic, name='topic'), # 用於添加新主題的頁面 path('new_topic/', views.new_topic, name='new_topic'), # 用於添加新條目的頁面 re_path(r'^new_entry/(?P\d+)/$', views.new_entry, name='new_entry'), # 用於編輯條目的頁面 re_path(r'^edit_entry/(?P\d+)/$', views.edit_entry, name='edit_entry'),]
在URL(如http://localhost:8000/edit_entry/1/
)中傳遞的ID存儲在形參entry_id
中。這個URL模式將預期匹配的請求發送給視圖函數edit_entry()
。
edit_entry()
:頁面edit_entry
收到GET請求時,edit_entry()
將返回一個表單,讓用戶可以對條目進行編輯。該頁面收到POST請求(條目文本通過修訂)時,它將修改後的文本保存到數據庫中。
# vim learning_logs/views.py
from django.shortcuts import renderfrom django.http import HttpResponseRedirectfrom django.urls import reversefrom .models import Topic, Entryfrom .forms import TopicForm, EntryFormdef index(request): """學習筆記的主頁""" return render(request, 'learning_logs/index.html')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/new_entry.html', context)
首先導入模型Entry 。接着獲取用戶要修改的條目對象,以及與該條目相關聯的主題。在請求方法爲GET時,咱們使用實參instance=entry
建立一個EntryForm實例。該實參讓Django建立一個表單,並使用既有條目對象中的信息填充它。用戶將看到既有的數據,並可以編輯它們。
處理POST請求時,傳遞實參instance=entry
和data=request.POST
,讓Django根據既有條目對象建立一個表單實例,並根據request.POST中的相關數據對其進行修改。而後檢查表單是否有效,若是有效就調用save()
且不指定任何實參。接下來重定向到顯示條目所屬主題的頁面,用戶將在其中看到其編輯的條目的新版本。
edit_entry
:# vim learning_logs/templates/learning_logs/edit_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 changesbutton> form>{% endblock content %}
edit_entry
:# vim learning_logs/templates/learning_logs/topic.html
{% 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 entrya> p> <ul> {% 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 entrya> p> li> {% empty %} <li> There are no entries for this topic yet. li> {% endfor %} ul>{% endblock content %}
將編輯連接放在每一個條目的日期和文本後面。在循環中,使用模板標籤
{% url %}
根據URL模式edit_entry
和當前條目的ID屬性entry.id
來肯定URL。連接文本爲"edit entry"
,它出如今頁面中每一個條目的後面。
應用路徑:users
接下來創建一個用戶註冊和身份驗證系統,讓用戶可以註冊帳戶,進而登陸和註銷。
# python manage.py startapp users# lsdb.sqlite3 learning_log learning_logs ll_env manage.py users# ls users/admin.py apps.py __init__.py migrations models.py tests.py views.py
能夠看到, users目錄的結構與應用程序learning_logs相同。
# vim learning_log/settings.pyINSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # 個人應用程序 'learning_logs', 'users', #增長這行]
# vim learning_log/urls.py
from django.contrib import adminfrom django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('users/', include('users.urls', namespace='users')), #增長這行 path('', include('learning_logs.urls', namespace='learning_logs')),]
一樣的,這裏保存完文件以後會報錯ModuleNotFoundError: No module named 'users.urls'
,不用理會,繼續往下作。
如今來實現登陸頁面的功能,可使用Django提供的默認登陸視圖。
# vim users/urls.py
"""定義users的URL模式"""from django.urls import path, re_pathfrom django.contrib.auth.views import LoginViewfrom . import views app_name='users'urlpatterns = [ # 登陸主頁 re_path(r'^login/$', LoginView.as_view(template_name='users/login.html'), name='login'),]
login.html
:# mkdir -p users/templates/users# vim users/templates/users/login.html
{% 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 inbutton> <input type="hidden" name="next" value="{% url 'learning_logs:index' %}" /> form> {% endblock content %}
先繼承base.html,請注意,一個應用程序中的模板可繼承另外一個應用程序中的模板。
若是表單的errors屬性被設置,咱們就顯示一條錯誤消息,指出輸入的用戶名—密碼對與數據庫中存儲的任何用戶名—密碼對都不匹配。
咱們要讓登陸視圖處理表單,所以將實參
action
設置爲登陸頁面的URL。登陸視圖將一個表單發送給模板,在模板中,咱們顯示這個表單並添加一個提交按鈕。後面包含了一個隱藏的表單元素’next
’,其中的實參value
告訴Django在用戶成功登陸後將其重定向到主頁。
# vim learning_logs/templates/learning_logs/base.html
<p> <a href="{% url 'learning_logs:index' %}">Learning Loga> - <a href="{% url 'learning_logs:topics' %}">Topicsa> - {% if user.is_authenticated %} Hello, {{ user.username }}. {% else %} <a href="{% url 'users:login' %}">log ina> {% endif %}p>{% block content %}{% endblock content %}
在Django身份驗證系統中,每一個模板均可使用變量
user
,該變量有一個is_authenticated
屬性:若是用戶已登陸,該屬性將爲True,不然爲False。在這裏向已登陸的用戶顯示一條問候語。對於已經過身份驗證的用戶設置了屬性
username
,咱們使用這個屬性來個性化問候語,讓用戶知道他已經登陸。而對於還未經過身份驗證的用戶,咱們再顯示一個到登陸頁面的連接。
訪問192.168.30.128:8000/users/login/
。若是以前使用超級用戶登陸的,註銷以後再訪問。
如今須要提供一個讓用戶註銷的途徑。咱們不須要建立用於註銷的頁面,而讓用戶只需單擊一個連接就能註銷並返回到主頁。爲此,咱們將爲註銷連接定義一個URL模式,編寫一個視圖函數,並在base.html中添加一個註銷連接。
# vim users/urls.py
"""定義users的URL模式"""from django.urls import path, re_pathfrom django.contrib.auth.views import LoginViewfrom . import views app_name='users'urlpatterns = [ # 登陸主頁 re_path(r'^login/$', LoginView.as_view(template_name='users/login.html'), name='login'), # 註銷 re_path(r'^logout/$', views.logout_view, name='logout'),]
這個URL模式將請求發送給函數logout_view()
,要將其與咱們下面在其中調用的函數logout()
區分開來。
logout_view()
:# vim users/views.py
from django.shortcuts import renderfrom django.http import HttpResponseRedirectfrom django.urls import reversefrom django.contrib.auth import logoutdef logout_view(request): """註銷用戶""" logout(request) return HttpResponseRedirect(reverse('learning_logs:index'))
先從django.contrib.auth中導入了函數logout()
;而後調用了函數logout()
,它要求將request對象做爲實參;最後重定向到主頁。
如今須要在base.html中添加註銷連接,讓每一個頁面都包含它;將它放在標籤{% if user.is_authenticated %}
中,使得僅當用戶登陸後才能看到它。
# vim learning_logs/templates/learning_logs/base.html
<p> <a href="{% url 'learning_logs:index' %}">Learning Loga> - <a href="{% url 'learning_logs:topics' %}">Topicsa> - {% if user.is_authenticated %} Hello, {{ user.username }}. <a href="{% url 'users:logout' %}">log outa> {% else %} <a href="{% url 'users:login' %}">log ina> {% endif %}p>{% block content %}{% endblock content %}
使用超級用戶登陸
下面使用Django提供的表單UserCreationForm來建立一個讓新用戶可以註冊的頁面。
# vim users/urls.py
"""定義users的URL模式"""from django.urls import path, re_pathfrom django.contrib.auth.views import LoginViewfrom . import views app_name='users'urlpatterns = [ # 登陸主頁 re_path(r'^login/$', LoginView.as_view(template_name='users/login.html'), name='login'), # 註銷 re_path(r'^logout/$', views.logout_view, name='logout'), # 註冊頁面 re_path(r'^register/$', views.register, name='register'),]
這個模式與URLhttp://localhost:8000/users/register/
匹配,並將請求發送給咱們即將編寫的函數register()
。
register()
:在註冊頁面首次被請求時,視圖函數register()
須要顯示一個空的註冊表單,並在用戶提交填寫好的註冊表單時對其進行處理。若是註冊成功,這個函數還需讓用戶自動登陸。
# vim users/views.py
from django.shortcuts import renderfrom django.http import HttpResponseRedirectfrom django.urls import reversefrom django.contrib.auth import login, logout, authenfrom django.contrib.auth.forms import UserCreationFormdef 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)
首先導入了函數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
傳遞給它,這將爲新用戶建立有效的會話。最後,咱們將用戶重定向到主頁,其頁眉中顯示了一條個性化的問候語,讓用戶知道註冊成功了。
register.html
:# vim users/templates/users/register.html
{% extends "learning_logs/base.html" %} {% block content %} <form method="post" action="{% url 'users:register' %}"> {% csrf_token %} {{ form.as_p }} <button name="submit">registerbutton> <input type="hidden" name="next" value="{% url 'learning_logs:index' %}" /> form> {% endblock content %}
# vim learning_logs/templates/learning_logs/base.html
<p> <a href="{% url 'learning_logs:index' %}">Learning Loga> - <a href="{% url 'learning_logs:topics' %}">Topicsa> - {% if user.is_authenticated %} Hello, {{ user.username }}. <a href="{% url 'users:logout' %}">log outa> {% else %} <a href="{% url 'users:register' %}">registera> - <a href="{% url 'users:login' %}">log ina> {% endif %}p>{% block content %}{% endblock content %}
如今,已登陸的用戶看到的是個性化的問候語和註銷連接,而未登陸的用戶看到的是註冊連接和登陸連接。
須要注意的是,這裏的註冊系統容許用戶建立任意數量的帳戶。實際狀況下的系統會要求用戶確認其身份:發送一封確認郵件或者用手機驗證碼,用戶回覆後其帳戶才生效。經過這樣作,系統生成的垃圾帳戶將比這裏使用的簡單系統少。
用戶應該可以輸入其專有的數據,所以咱們將建立一個系統,肯定各項數據所屬的用戶,再限制對頁面的訪問,讓用戶只能使用本身的數據。
下面將修改模型Topic ,讓每一個主題都歸屬於特定用戶。這也將影響條目,由於每一個條目都屬於特定的主題。
Django提供了裝飾器@login_required
,讓你可以輕鬆地實現這樣的目標:對於某些頁面,只容許已登陸的用戶訪問它們。裝飾器(decorator)是放在函數定義前面的指令,Python在函數運行前,根據它來修改函數代碼的行爲。
# vim learning_logs/views.py
from django.shortcuts import renderfrom django.http import HttpResponseRedirectfrom django.urls import reversefrom django.contrib.auth.decorators import login_requiredfrom .models import Topic, Entryfrom .forms import TopicForm, EntryFormdef index(request): """學習筆記的主頁""" return render(request, 'learning_logs/index.html')@login_requireddef 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/new_entry.html', context)
首先導入函數login_required()
,將login_required()
做爲裝飾器用於視圖函數topics()
——在它前面加上符號@
和login_required
,讓Python在運行topics()
的代碼前先運行login_required()
的代碼。
login_required()
的代碼檢查用戶是否已登陸,僅當用戶已登陸時,Django才運行topics()
的代碼。若是用戶未登陸,就重定向到登陸頁面。
爲了實現未登陸的重定向,須要修改settings.py。
# vim learning_log/settings.py #末尾添加# 個人設置LOGIN_URL = '/users/login/'
能夠看到,如今直接點擊topics
會直接跳轉到登陸頁。
在項目「學習筆記」中,咱們將不限制主頁、註冊頁面和註銷頁面的訪問,同時限制其它全部頁面的訪問。
# vim learning_logs/views.py
from django.shortcuts import renderfrom django.http import HttpResponseRedirectfrom django.urls import reversefrom django.contrib.auth.decorators import login_requiredfrom .models import Topic, Entryfrom .forms import TopicForm, EntryFormdef index(request): """學習筆記的主頁""" return render(request, 'learning_logs/index.html')@login_requireddef topics(request): """顯示全部主題""" topics = Topic.objects.order_by('date_added') context = {'topics': topics} return render(request, 'learning_logs/topics.html', context)@login_requireddef 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)@login_requireddef 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)@login_requireddef 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)@login_requireddef 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/new_entry.html', context)
若是你在未登陸的狀況下嘗試訪問這些頁面,將被重定向到登陸頁面。此外,你還不能單擊到new_topic等頁面的連接,若是你輸入URL http://192.168.30.128:8000/new_topic/
,將重定向到登陸頁面。對於全部與私有用戶數據相關的URL,都應限制對它們的訪問。
如今將數據關聯到提交它們的用戶。咱們只需將最高層的數據關聯到用戶,這樣更低層的數據將自動關聯到用戶。在項目「學習筆記」中,應用程序的最高層數據是主題,而全部條目都與特定主題相關聯。只要每一個主題都歸屬於特定用戶,咱們就能肯定數據庫中每一個條目的全部者。
# vim learning_logs/models.py
from django.db import modelsfrom django.contrib.auth.models import Userclass Topic(models.Model): """用戶學習的主題""" text = models.CharField(max_length=200) date_added = models.DateTimeField(auto_now_add=True) owner = models.ForeignKey(User, on_delete=models.CASCADE) def __str__(self): """返回模型的字符串表示""" return self.text class Entry(models.Model): """學到的有關某個主題的具體知識""" topic = models.ForeignKey(Topic, on_delete=models.CASCADE) text = models.TextField() date_added = models.DateTimeField(auto_now_add=True) class Meta: verbose_name_plural = 'entries' def __str__(self): """返回模型的字符串表示""" return self.text[:50] + "..."
首先導入了django.contrib.auth
中的模型User,而後在Topic中添加了字段owner,它創建到模型User的外鍵關係。
遷移數據庫時,Django將對數據庫進行修改,使其可以存儲主題和用戶之間的關聯。爲執行遷移,Django須要知道該將各個既有主題關聯到哪一個用戶。最簡單的辦法是,將既有主題都關聯到同一個用戶,如超級用戶。爲此,咱們須要知道用戶的ID。
# python manage.py shell>>> from django.contrib.auth.models import User>>> User.objects.all()<QuerySet [<User: ll_admin>, <User: lzx>]>>>> for user in User.objects.all():... print(user.username, user.id)... ll_admin 1 lzx 2
# python manage.py makemigrations learning_logsSelect an option: 1 #選擇第1個選項——如今提供默認值>>> 1 #輸入用戶IDMigrations for 'learning_logs': learning_logs/migrations/0003_topic_owner.py - Add field owner to topic
將全部既有主題都關聯到管理用戶ll_admin,我輸入了用戶ID值1。並不是必須使用超級用戶,也可以使用已建立的任何用戶的ID。
# python manage.py migrate #執行遷移# python manage.py shell>>> from learning_logs.models import Topic>>> for topic in Topic.objects.all():... print(topic, topic.owner)... Chess ll_admin Rock Climbing ll_admin
能夠看到,每一個主題的全部者是超級用戶ll_admin。
注意:也能夠重置數據庫而不是遷移它,但若是這樣作,既有的數據都將丟失。若是確實想要一個新的數據庫,能夠執行命令
python manage.py flush
,這會重建數據庫的結構。若是你這樣作,就須要從新建立超級用戶,且原來的全部數據都將丟失。
views.py
:# vim learning_logs/views.py
@login_requireddef topics(request): """顯示全部主題""" topics = Topic.objects.filter(owner=request.user).order_by('date_added') #修改 context = {'topics': topics} return render(request, 'learning_logs/topics.html', context)
用戶登陸後,request對象將有一個user
屬性,這個屬性存儲了有關該用戶的信息。代碼Topic.objects.filter(owner=request.user)
讓Django只從數據庫中獲取owner
屬性爲當前用戶的Topic對象。
要查看結果,以全部既有主題關聯到的用戶的身份登陸(這裏咱們是ll_admin)並訪問topics頁面,你將看到全部的主題。而後,註銷並以另外一個用戶的身份登陸,topics頁面將不會列出任何主題。
接下來限制對單個主題的頁面的訪問。
views.py
:# vim learning_logs/views.py
from django.http import HttpResponseRedirect, Http404 #導入Http404@login_requireddef 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)
服務器上沒有請求的資源時,標準的作法是返回404響應。首先導入了異常Http404,並在用戶請求它不能查看的主題時引起這個異常。收到主題請求後,在渲染網頁前檢查該主題是否屬於當前登陸的用戶。若是請求的主題不歸當前用戶全部,咱們就引起Http404異常,讓Django返回一個404錯誤頁面。
edit_entry
頁面edit_entry
的URL爲http://192.168.30.128:8000/edit_entry/entry_id/
,其中entry_id
是一個數字。下面來保護這個頁面,禁止用戶經過輸入相似於前面的URL來訪問其餘用戶的條目。
views.py
:# vim learning_logs/views.py
@login_requireddef 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': # 初次請求,使用當前條目填充表單 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/new_entry.html', context)
獲取指定的條目以及與之相關聯的主題,而後檢查主題的全部者是不是當前登陸的用戶,若是不是,就引起Http404異常。
當前,用於添加新主題的頁面存在問題。若是你嘗試添加新主題,將看到錯誤消息IntegrityError,指出learning_logs_topic.user_id
不能爲NULL 。Django的意思是說,建立新主題時,你必須指定其owner字段的值。
views.py
:# vim learning_logs/views.py
@login_requireddef 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)
首先調用form.save()
,並傳遞實參commit=False
,由於咱們先修改新主題,再將其保存到數據庫中。
接下來,將新主題的owner
屬性設置爲當前用戶。最後,對剛定義的主題實例調用save()
。如今主題包含全部必不可少的數據,將被成功地保存。
如今,這個項目容許任何用戶註冊,而每一個用戶想添加多少新主題均可以。但每一個用戶都只能訪問本身的數據,不管是查看數據、輸入新數據仍是修改舊數據時都如此。