你將建立一些表單,讓用戶可以添加主題和條目,以及編輯既有的條目。你還將學習Django如何防範對基於表單的網頁發起的常見攻擊,這讓你無需花太多時間考慮確保應用程序安全的問題。html
而後,咱們將實現一個用戶身份驗證系統。你將建立一個註冊頁面,供用戶建立帳戶,並讓有些頁面只能供已登陸的用戶訪問。接下來,咱們將修改一些視圖函數, 使得用戶只能看到本身的數據。你將學習如何確保用戶數據的安全。python
創建用於建立用戶帳戶的身份驗證系統以前,咱們先來添加幾個頁面,讓用戶可以輸入數據。咱們將讓用戶可以添加新主題、添加新條目以及編輯既有條目。
當前,只有超級用戶可以經過管理網站輸入數據。咱們不想讓用戶與管理網站交互,所以咱們將使用Django的表單建立工具來建立讓用戶可以輸入數據的頁面。數據庫
建立基於表單的頁面的方法幾乎與前面建立網頁同樣:定義一個URL,編寫一個視圖函數並編寫一個模板。一個主要差異是,須要導入包含表單的模塊forms.py。django
(1)建立表單編程
讓用戶輸入並提交信息的頁面都是表單,哪怕它看起來不像表單。用戶輸入信息時,咱們須要進行驗證,確認提供的信息是正確的數據類型,且不是惡意的信息,如中斷服務器的代碼。而後,咱們再對這些有效信息進行處理,並將其保存到數據庫的合適地方。這些工做不少都是由Django自動完成的。 在Django中,建立表單的最簡單方式是使用ModelForm,它根據咱們在python-Django實踐的模型中的信息自動建立表單。建立一個名爲forms.py的文件,將其存儲到models.py所在的目錄
中,並在其中編寫你的第一個表單:瀏覽器
from django import forms from .models import Topic class TopicForm(forms.ModelForm): class Meta: model = Topic fields = ['text'] labels = {'text': ''}
咱們首先導入了模塊forms 以及要使用的模型Topic 。咱們定義了一個名爲TopicForm 的類,它繼承了forms.ModelForm 。最簡單的ModelForm 版本只包含一個內嵌的Meta 類,它告訴Django根據哪一個模型建立表單,以及在表單中包含哪些字段。咱們根據模型Topic 建立一個表單,該表單只包含字段text (的代碼讓Django不要爲字段text 生成標籤。安全
(2)URL模式new_topic服務器
這個新網頁的URL應簡短而具備描述性,所以當用戶要添加新主題時,咱們將切換到http://localhost:8000/new_topic/。下面是網頁new_topic 的URL模式,咱們將其添加到 learning_logs/urls.py中:app
"""定義learning_logs的URL模式""" from django.conf.urls import url from . import views app_name='learning_logs' urlpatterns = [ # 主頁 url(r'^$', views.index, name='index'), # 顯示全部的主題 url(r'^topics/$', views.topics, name='topics'), # 制定主題的詳細頁面 url(r'^topics/(?P<topic_id>\d+)/$', views.topic, name='topic'), # 用於添加新主題的網頁 url(r'^new_topic/$', views.new_topic, name='new_topic'), ]
(3)視圖函數new_topic()函數
修改views.py,函數new_topic() 須要處理兩種情形:剛進入new_topic 網頁(在這種狀況下,它應顯示一個空表單);對提交的表單數據進行處理,並將用戶重定向到網頁topics :
from django.shortcuts import render from django.http import HttpResponseRedirect from django.urls import reverse from .models import Topic from .forms import TopicForm def 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 類,用戶提交主題後咱們將使用這個類將用戶重定向到網頁topics 。函數reverse() 根據指定的URL模型肯定URL,這意味着Django 將在頁面被請求時生成URL。咱們還導入了剛纔建立的表單TopicForm 。
(4)GET請求和POST請求
建立Web應用程序時,將用到的兩種主要請求類型是GET請求和POST請求。對於只是從服務器讀取數據的頁面,使用GET請求;在用戶須要經過表單提交信息時,一般使用POST請求。
處理全部表單時,咱們都將指定使用POST方法。還有一些其餘類型的請求,但這個項目沒有使用。
函數new_topic() 將請求對象做爲參數。用戶初次請求該網頁時,其瀏覽器將發送GET請求;
用戶填寫並提交表單時,其瀏覽器將發送POST請求。
根據請求的類型,咱們能夠肯定用戶請求的是空表單(GET請求)仍是要求對填寫好的表單進行處理(POST請求)。
❶處的測試肯定請求方法是GET仍是POST。若是請求方法不是POST,請求就多是GET,所以咱們須要返回一個空表單(即使請求是其餘類型的,返回一個空表單也不會有任何 問題)。咱們建立一個TopicForm 實例(見❷),將其存儲在變量form 中,再經過上下文字典將這個表單發送給模板context(見❼)。因爲實例化TopicForm 時咱們沒有指定任何 實參,Django將建立一個可供用戶填寫的空表單。
若是請求方法爲POST,將執行else 代碼塊,對提交的表單數據進行處理。咱們使用用戶輸入的數據(它們存儲在request.POST 中)建立一個TopicForm 實例(見❸), 這樣對象form 將包含用戶提交的信息。
要將提交的信息保存到數據庫,必須先經過檢查肯定它們是有效的(見❹)。函數is_valid() 覈實用戶填寫了全部必不可少的字段(表單字段默認都是必不可少的),且輸入 的數據與要求的字段類型一致(例如,字段text 少於200個字符,這是咱們在第18章中的models.py中指定的)。這種自動驗證避免了咱們去作大量的工做。若是全部字段都有 效,咱們就可調用save() (見❺),將表單中的數據寫入數據庫。保存數據後,就可離開這個頁面了。咱們使用reverse() 獲取頁面topics 的URL,並將其傳遞 給HttpResponseRedirect() (見❻),後者將用戶的瀏覽器重定向到頁面topics 。在頁面topics 中,用戶將在主題列表中看到他剛輸入的主題。
5. 模板new_topic
下面來建立新模板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 topic</button> </form> {% endblock content %}
這個模板繼承了base.html,所以其基本結構與項目「學習筆記」的其餘頁面相同。
六、連接到頁面new_topic
接下來,咱們在頁面topics.html 中添加一個到頁面new_topic 的鏈:
{% extends "learning_logs/base.html" %} {% block content %} <p>Topics</p> <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,編寫視圖函數和模板,並連接到添加新條目的網頁。但在此以前,咱們須要在forms.py中再添加一個 類。
(1)用於添加新條目的表單
咱們須要在forms.py建立一個與模型Entry 相關聯的表單,但這個表單的定製程度比TopicForm 要高些:
from django import forms from .models import Topic, Entry class TopicForm(forms.ModelForm): class Meta: model = Topic fields = ['text'] labels = {'text': ''} class EntryForm(forms.ModelForm): class Meta: model = Entry fields = ['text'] labels = {'text': ''} widgets = {'text': forms.Textarea(attrs={'cols': 80})}
(2) URL模式new_entry
在用於添加新條目的頁面的URL模式中,須要包含實參topic_id ,由於條目必須與特定的主題相關聯。該URL模式以下,咱們將它添加到了learning_logs/urls.py中:
"""定義learning_logs的URL模式""" from django.conf.urls import url from . import views app_name='learning_logs' urlpatterns = [ # 主頁 url(r'^$', views.index, name='index'), # 顯示全部的主題 url(r'^topics/$', views.topics, name='topics'), # 制定主題的詳細頁面 url(r'^topics/(?P<topic_id>\d+)/$', views.topic, name='topic'), # 用於添加新主題的網頁 url(r'^new_topic/$', views.new_topic, name='new_topic'), # 用於添加新條目的頁面 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 中。請求的URL與這個模式匹配時,Django將請求和主題ID發送給函數new_entry() 。
(3)視圖函數new_entry()
視圖函數new_entry() 與函數new_topic() 很像,在views.py中添加:
from django.shortcuts import render from django.http import HttpResponseRedirect from django.urls import reverse from .models import Topic from .forms import TopicForm,EntryForm def 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)
(4)模板new_entry
模板new_entry.html 相似於模板new_topic.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 entry</button> </form> {% endblock content %}
咱們在頁面頂端顯示了主題,讓用戶知道他是在哪一個主題中添加條目;該主題名也是一個連接,可用於返回到該主題的主頁面。 表單的實參action 包含URL中的topic_id 值,讓視圖函數可以將新條目關聯到正確的主題。除此以外,這個模板與模板new_topic.html徹底相同。
(5)連接到頁面new_entry
咱們須要在topic.html顯示特定主題的頁面中添加到頁面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> {% 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 %}
咱們在顯示條目前添加連接,由於在這種頁面中,執行的最多見的操做是添加新條目。下圖顯示了頁面new_entry 。如今用戶能夠添加新主題,還能夠在每一個主題中添加任 意數量的條目。請在一些既有主題中添加一些新條目,嘗試使用一下頁面new_entry 。
下面來建立一個頁面,讓用戶可以編輯既有的條目。
(1)URL模式edit_entry
這個頁面的URL須要傳遞要編輯的條目的ID。修改後的learning_logs/urls.py以下:
"""定義learning_logs的URL模式""" from django.conf.urls import url from . import views app_name='learning_logs' urlpatterns = [ # 主頁 url(r'^$', views.index, name='index'), # 顯示全部的主題 url(r'^topics/$', views.topics, name='topics'), # 制定主題的詳細頁面 url(r'^topics/(?P<topic_id>\d+)/$', views.topic, name='topic'), # 用於添加新主題的網頁 url(r'^new_topic/$', views.new_topic, name='new_topic'), # 用於添加新條目的頁面 url(r'^new_entry/(?P<topic_id>\d+)/$', views.new_entry, name='new_entry'), # 用戶編輯條目的頁面 url(r'^edit_entry/(?P<entry_id>\d+)/$', views.edit_entry, name='edit_entry'), ]
在URL(如http://localhost:8000/edit_entry/1/)中傳遞的ID存儲在形參entry_id 中。這個URL模式將預期匹配的請求發送給視圖函數edit_entry() 。
(2)視圖函數edit_entry()
修改視圖views.py,頁面edit_entry 收到GET請求時,edit_entry() 將返回一個表單,讓用戶可以對條目進行編輯。該頁面收到POST請求(條目文本通過修訂)時,它將修改後的文本保存到數據庫中:
from django.shortcuts import render from django.http import HttpResponseRedirect from django.urls import reverse from .models import Topic, Entry from .forms import TopicForm,EntryForm def 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/edit_entry.html', context)
咱們首先須要導入模型Entry 。咱們獲取用戶要修改的條目對象,以及與該條目相關聯的主題。在請求方法爲GET時將執行的if 代碼塊中,咱們使用實 參instance=entry 建立一個EntryForm 實例。這個實參讓Django建立一個表單,並使用既有條目對象中的信息填充它。用戶將看到既有的數據,並可以編輯它們。
處理POST請求時,咱們傳遞實參instance=entry 和data=request.POST ,讓Django根據既有條目對象建立一個表單實例,並根據request.POST 中的相關數 據對其進行修改。而後,咱們檢查表單是否有效,若是有效,就調用save() ,且不指定任何實參。接下來,咱們重定向到顯示條目所屬主題的頁面,用戶將 在其中看到其編輯的條目的新版本。
(3)模板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 %}
實參action將表單發回給函數edit_entry()進行處理。在標籤{% url %}中,咱們將條目ID做爲一個實參,讓視圖對象可以修改正確的條目對象。咱們將提交按 鈕命名爲save changes,以提醒用戶:單擊該按鈕將保存所作的編輯,而不是建立一個新條目。
(4) 連接到頁面edit_entry
在顯示特定主題的頁面中,須要給每一個條目添加到頁面edit_entry 的連接,修改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 entry</a> </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 entry</a> </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",它出如今頁面中每一個條目的後面。下圖顯示了包含這些連接時,顯示特定主題的頁面是什麼樣的。
參考:
一、python編程,從入門到實踐