Django web編程2 -- 編輯頁面內容

你將建立一些表單,讓用戶可以添加主題和條目,以及編輯既有的條目。你還將學習Django如何防範對基於表單的網頁發起的常見攻擊,這讓你無需花太多時間考慮確保應用程序安全的問題。html

而後,咱們將實現一個用戶身份驗證系統。你將建立一個註冊頁面,供用戶建立帳戶,並讓有些頁面只能供已登陸的用戶訪問。接下來,咱們將修改一些視圖函數, 使得用戶只能看到本身的數據。你將學習如何確保用戶數據的安全。python

一、讓用戶可以輸入數據

創建用於建立用戶帳戶的身份驗證系統以前,咱們先來添加幾個頁面,讓用戶可以輸入數據。咱們將讓用戶可以添加新主題、添加新條目以及編輯既有條目。
當前,只有超級用戶可以經過管理網站輸入數據。咱們不想讓用戶與管理網站交互,所以咱們將使用Django的表單建立工具來建立讓用戶可以輸入數據的頁面。數據庫

1.1 添加新主題

建立基於表單的頁面的方法幾乎與前面建立網頁同樣:定義一個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,所以其基本結構與項目「學習筆記」的其餘頁面相同。

  • 實參action 告訴服務器將提交的表單數據發送到哪裏,這 裏咱們將它發回給視圖函數new_topic() 。實參method 讓瀏覽器以POST請求的方式提交數據。
  • Django使用模板標籤{% csrf_token %} 來防止攻擊者利用表單來得到對服務器未經受權的訪問(這種攻擊被稱爲跨站請求僞造 )
  • 咱們只需包含模板變量{{ form.as_p }},就可以讓Django自動建立顯示錶單所需的所有字段。修飾符as_p讓Django以段落格式渲 染全部表單元素,這是一種整潔地顯示錶單的簡單方式。
  • Django不會爲表單建立提交按鈕,所以定義了一個這樣的按鈕。

六、連接到頁面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 %}

這個連接放在了既有主題列表的後面。下圖顯示了生成的表單。請使用這個表單來添加幾個新主題。

1.2 添加新條目

如今用戶能夠添加新主題了,但他們還想添加新條目。咱們將再次定義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})}
  • 咱們首先修改了import 語句,使其除導入Topic 外,還導入Entry 。新類EntryForm 繼承了forms.ModelForm ,它包含的Meta 類指出了表單基於的模型以及要在表單 中包含哪些字段。這裏也給字段'text' 指定了一個空標籤。
  • 咱們定義了屬性widgets 。小部件 (widget)是一個HTML表單元素,如單行文本框、多行文本區域或下拉列表。經過設置屬性widgets ,可覆蓋Django選擇的默認小 部件。經過讓Django使用forms.Textarea ,咱們定製了字段'text' 的輸入小部件,將文本區域的寬度設置爲80列,而不是默認的40列。這給用戶提供了足夠的空間,能夠 編寫有意義的條目。

(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)
  • 咱們修改了import 語句,在其中包含了剛建立的EntryForm 。new_entry() 的定義包含形參topic_id ,用於存儲從URL中得到的值。渲染頁面以及處理表單數據時,都 須要知道針對的是哪一個主題,所以咱們使用topic_id 來得到正確的主題。
  • 咱們檢查請求方法是POST仍是GET。若是是GET請求,將執行if 代碼塊:建立一個空的EntryForm 實例。若是請求方法爲POST,咱們就對數據進行處理: 建立一個EntryForm 實例,使用request 對象中的POST數據來填充它;再檢查表單是否有效,若是有效,就設置條目對象的屬性topic ,再將條目對象保存到數據 庫。
  • 調用save() 時,咱們傳遞了實參commit=False ,讓Django建立一個新的條目對象,並將其存儲到new_entry 中,但不將它保存到數據庫中。咱們將new_entry 的屬性topic 設置爲在這個函數開頭從數據庫中獲取的主題,而後調用save() ,且不指定任何實參。這將把條目保存到數據庫,並將其與正確的主題相關聯。
  • 咱們將用戶重定向到顯示相關主題的頁面。調用reverse() 時,須要提供兩個實參:要根據它來生成URL的URL模式的名稱;列表args ,其中包含要包含在URL中的 全部實參。在這裏,列表args 只有一個元素——topic_id 。接下來,調用HttpResponseRedirect() 將用戶重定向到顯示新增條目所屬主題的頁面,用戶將在該頁面的 條目列表中看到新添加的條目。

(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.3 編輯條目

下面來建立一個頁面,讓用戶可以編輯既有的條目。

(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編程,從入門到實踐

相關文章
相關標籤/搜索