19.1 讓用戶能夠輸入數(shù)據(jù)
19.1.1 添加新主題
創(chuàng)建基于表單的頁面的方法幾乎與前面創(chuàng)建網(wǎng)頁一樣: 定義URL佛点,編寫一個視圖函數(shù)并編寫一個模板醇滥。一個主要差別是黎比,需要導(dǎo)入包含表單的模塊 forms.py
1. 用于添加主題的表單
讓用戶輸入并提交信息的頁面都是表單,表單的很多工作都是由Django自動完成的鸳玩,比如:
- 用戶輸入信息時阅虫,我們需要進行驗證,確認提供的信息是正確的數(shù)據(jù)類型不跟,且不是惡意的信息颓帝。
- 對這些有效信息進行處理,并將其保存到數(shù)據(jù)庫的合適地方
在Django中窝革,創(chuàng)建表單的最簡單方式是使用ModelForm购城, 它根據(jù)我們定義的模型中的信息自動創(chuàng)建表單。創(chuàng)建一個名為forms.py文件虐译,并將其存儲到models.py所在的目錄中瘪板, 并在其中編寫第一個表單
# forms.py
from django import forms
from .models import Topic
class TopicForm(forms.ModelForm):
class Meta:
model = Topic
fields = ['text']
labels = {'text': ''}
首先導(dǎo)入了模塊forms以及要使用的模型Topic,我們定義了一個名為TopicForm的類漆诽,它繼承了forms.ModelForm侮攀。
最簡單的ModelForm版本只包含一個內(nèi)嵌的 Meta 類,它告訴Django根據(jù)哪個模型創(chuàng)建表單厢拭,以及在表單中包含哪些字段兰英。根據(jù)模型Topic創(chuàng)建一個表單,該表單只包含字段text供鸠,labels代碼讓Django不要為字段text生成標簽畦贸。
2. URL 模式 new_topic
當用戶要添加新主題時,我們將切換到http://localhost:8000/new_topic/楞捂,添加URL模式到learning_logs/urls.py
# urls.py
from django.conf.urls import url
from . import views
app_name = 'learning_logs'
urlpatterns = [
# index
url(r'^$', views.index, name='index'),
# show all topics
url(r'^topics/$', views.topics, name='topics'),
# show detail info for specific topic
url(r'^topics/(?P<topic_id>\d+)/$', views.topic, name='topic'),
# add new topic page
url(r'^new_topic/$', views.new_topic, name='new_topic'),
]
3.視圖函數(shù)new_topic()
函數(shù)new_topic()需要處理兩種情況:剛進入new_topic網(wǎng)頁(在這種情況下家制,它應(yīng)顯示一個空表單);對提交的表單數(shù)據(jù)進行處理泡一,并將用戶重定向到網(wǎng)頁topics
# 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
# Create your views here.
def index(request):
return render(request, 'learning_logs/index.html')
def topics(request):
"""show all topics"""
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':
# 未提交數(shù)據(jù): 創(chuàng)建一個新表單
form = TopicForm()
else:
# POST提交的數(shù)據(jù)颤殴, 對數(shù)據(jù)進行處理
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)
如果請求方法是POST, 對提交的表單數(shù)據(jù)進行處理鼻忠。我們使用用戶輸入的數(shù)據(jù)(它們存儲在request.POST中)創(chuàng)建一個TopicForm實例涵但,這樣對象form將包含用戶提交的信息杈绸。
要將提交的信息保存到數(shù)據(jù)庫,必須通過檢查確定它們是有效的矮瘟。函數(shù)is_valid()自動驗證避免了我們?nèi)プ龃罅康墓ぷ魍АH绻凶侄味加行В覀兙涂烧{(diào)用save()澈侠,將表單中的數(shù)據(jù)寫入數(shù)據(jù)庫劫侧。保存數(shù)據(jù)后,就可離開這個頁面哨啃,使用reverse()獲取topics的URL烧栋。
4. 模板 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 %}
實參action告訴服務(wù)器將提交的表單數(shù)據(jù)發(fā)送到哪里,這里我們將它發(fā)送回視圖函數(shù)new_topic()拳球。
Django使用模板標簽{% csrf_token %}來防止攻擊者利用表單來獲取對服務(wù)器未經(jīng)授權(quán)的訪問审姓。 為了顯示表單,我們只需要包含模板變量{{ form.as_p }}祝峻,就可讓Django自動創(chuàng)建顯示表單所需的全部字段魔吐。修飾符as_p讓Django以段落格式渲染所有表單元素,這是一種整潔地顯示表單的簡單方式莱找。
另外酬姆,Django 不會為表單創(chuàng)建提交按鈕。
5. 鏈接到頁面 new_topic
接下來奥溺,我們在頁面topics中添加一個到頁面new_topic的鏈接
# topics.html
{% 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 %}
19.1.2 添加新條目
現(xiàn)在用戶可以添加新主題了辞色,但他們還想添加新條目。我們將再次定義URL谚赎,編寫視圖和模板淫僻,并了解到添加新條目的網(wǎng)頁。但在此之前壶唤,我們需要在forms.py中再添加一個類雳灵。
** 1. 用于添加新條目的表單**
# forms.py
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})}
我們定義了屬性widgets, widget是HTML表單元素,如單行文本框闸盔,多行文本趨于或下拉了表悯辙。通過設(shè)置屬性widgets,可覆蓋Django選擇的默認小部件迎吵。通過讓Django使用forms.Textarea躲撰,我們定制了'text'的輸入小部件,將文本區(qū)域的寬度設(shè)置為80列击费,而不是默認的40列拢蛋。
2. URL模式new_entry
在用于添加新條目的頁面的URL模式中,需要包含實參topic_id蔫巩, 因此條目必須與特定的主題相關(guān)聯(lián)谆棱。我們將它添加到learning_logs/urls.py中
from django.conf.urls import url
from . import views
app_name = 'learning_logs'
urlpatterns = [
# index
url(r'^$', views.index, name='index'),
# show all topics
url(r'^topics/$', views.topics, name='topics'),
# show detail info for specific topic
url(r'^topics/(?P<topic_id>\d+)/$', views.topic, name='topic'),
# add new topic page
url(r'^new_topic/$', views.new_topic, name='new_topic'),
# add new entry page
url(r'new_entry/(?P<topic_id>\d+)/$', views.new_entry, name='new_entry'),
]
代碼(?P<topic_id>\d+)捕獲一個數(shù)字值快压,并將其存儲在變量topic_id中。 請求的URL與這個模式匹配時垃瞧,Django將請求和主題ID發(fā)送給函數(shù)new_entry()
** 3. 視圖函數(shù)new_entry()**
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse
from .models import Topic
from .forms import TopicForm, EntryForm
# Create your views here.
def index(request):
return render(request, 'learning_logs/index.html')
def topics(request):
"""show all topics"""
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':
# 未提交數(shù)據(jù): 創(chuàng)建一個新表單
form = TopicForm()
else:
# POST提交的數(shù)據(jù)蔫劣, 對數(shù)據(jù)進行處理
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':
# 未提交數(shù)據(jù),創(chuàng)建一個空表單
form = EntryForm()
else:
# POST提交的數(shù)據(jù)个从,對數(shù)據(jù)進行處理
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)
調(diào)用save()時脉幢,我們傳遞了實參commit=False,讓Django創(chuàng)建一個新的條目對象嗦锐,并將其存儲到ew_entry中嫌松,但不將它保存到數(shù)據(jù)庫中。我們將new_entry的屬性topic設(shè)置為在這個函數(shù)開頭從數(shù)據(jù)庫中獲取的主題意推,然后調(diào)用save()豆瘫。
4. 模板new_entry
# 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 entry</button>
</form>
{% endblock content %}
5. 鏈接到頁面new_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>
</li>
{% empty %}
<li>
There are no entries for this topic yet.
</li>
{% endfor %}
</ul>
{% endblock content %}
19.1.3 編輯條目
下面創(chuàng)建一個頁面珊蟀,讓用戶能夠編輯既有條目
1. URL模式edit_entry
修改后的learning_logs/urls.py
from django.conf.urls import url
from . import views
app_name = 'learning_logs'
urlpatterns = [
# index
url(r'^$', views.index, name='index'),
# show all topics
url(r'^topics/$', views.topics, name='topics'),
# show detail info for specific topic
url(r'^topics/(?P<topic_id>\d+)/$', views.topic, name='topic'),
# add new topic page
url(r'^new_topic/$', views.new_topic, name='new_topic'),
# add new entry page
url(r'new_entry/(?P<topic_id>\d+)/$', views.new_entry, name='new_entry'),
# edit entry page
url(r'edit_entry/(?P<entry_id>\d+)/$', views.edit_entry, name='edit_entry')
]
2.視圖函數(shù)edit_entry()
頁面edit_entry收到GET請求時菊值,edit_entry()將返回一個表單,讓用戶能夠?qū)l目進行編輯育灸。該頁面收到POST請求時腻窒,它將修改后的文本保存到數(shù)據(jù)庫中
# views.py
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
# Create your views here.
def index(request):
return render(request, 'learning_logs/index.html')
def topics(request):
"""show all topics"""
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':
# 未提交數(shù)據(jù): 創(chuàng)建一個新表單
form = TopicForm()
else:
# POST提交的數(shù)據(jù), 對數(shù)據(jù)進行處理
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':
# 未提交數(shù)據(jù)磅崭,創(chuàng)建一個空表單
form = EntryForm()
else:
# POST提交的數(shù)據(jù)儿子,對數(shù)據(jù)進行處理
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提交表單砸喻,對數(shù)據(jù)進行處理
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)
在請求方法為GET時將執(zhí)行的if代碼塊中柔逼,我們使用實參instance=entry創(chuàng)建一個EntryForm實例。這個實參讓Django創(chuàng)建一個表達割岛,并使用既有條目對象中的信息填充它愉适。處理POST請求時,我們傳遞實參instance=entry和data=request.POST癣漆,讓Django根據(jù)既有條目對象創(chuàng)建一個表單實例维咸,并根據(jù)request.POST中的相關(guān)數(shù)據(jù)對其進行修改。
3. 模板 edit_entry.html
# 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 changes</button>
</form>
{% endblock content %}
4.鏈接到頁面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 %}