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


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

然后,我們將實現一個用戶身份驗證系統。你將創建一個注冊頁面,供用戶創建賬戶,並讓有些頁面只能供已登錄的用戶訪問。接下來,我們將修改一些視圖函數, 使得用戶只能看到自己的數據。你將學習如何確保用戶數據的安全。

1、讓用戶能夠輸入數據

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

1.1 添加新主題

創建基於表單的頁面的方法幾乎與前面創建網頁一樣:定義一個URL,編寫一個視圖函數並編寫一個模板。一個主要差別是,需要導入包含表單的模塊forms.py。

(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中:

"""定義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不會為表單創建提交按鈕,因此定義了一個這樣的按鈕。

6、鏈接到頁面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",它出現在頁面中每個條目的后面。下圖顯示了包含這些鏈接時,顯示特定主題的頁面是什么樣的。

 

 

參考:

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


免責聲明!

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



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