Django Web應(yīng)用程序(四)

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 %}
添加新條目new_entry頁面

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 %}
每個條目都有一個用于對其進行編輯的鏈接
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末惠爽,一起剝皮案震驚了整個濱河市癌蓖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌婚肆,老刑警劉巖租副,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異较性,居然都是意外死亡用僧,警方通過查閱死者的電腦和手機讨越,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來永毅,“玉大人把跨,你說我怎么就攤上這事≌铀溃” “怎么了着逐?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長意蛀。 經(jīng)常有香客問我耸别,道長,這世上最難降的妖魔是什么县钥? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任秀姐,我火速辦了婚禮,結(jié)果婚禮上若贮,老公的妹妹穿的比我還像新娘省有。我一直安慰自己,他們只是感情好谴麦,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布蠢沿。 她就那樣靜靜地躺著,像睡著了一般匾效。 火紅的嫁衣襯著肌膚如雪舷蟀。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天面哼,我揣著相機與錄音野宜,去河邊找鬼。 笑死魔策,一個胖子當著我的面吹牛匈子,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播代乃,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼旬牲,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了搁吓?” 一聲冷哼從身側(cè)響起原茅,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎堕仔,沒想到半個月后擂橘,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡摩骨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年通贞,在試婚紗的時候發(fā)現(xiàn)自己被綠了朗若。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡昌罩,死狀恐怖哭懈,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情茎用,我是刑警寧澤遣总,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站轨功,受9級特大地震影響旭斥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜古涧,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一垂券、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧羡滑,春花似錦菇爪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽也祠。三九已至昙楚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間诈嘿,已是汗流浹背堪旧。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留奖亚,地道東北人淳梦。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像昔字,于是被迫代替她去往敵國和親爆袍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容