10、頁面側邊欄:使用自定義模板標簽 我們的博客側邊欄有四項內容:最新文章、歸檔、分類和標簽雲。這些內容相對比較固定,且在各個頁面都會顯示,如果像文章列表或者文章詳情一樣,從視圖函數中獲取然後傳遞給模板,則每個頁面對應的視圖函數里都要寫一段獲取這些內容的代碼,這會導致很多重覆代碼。更好的解決方案是直 ...
10、頁面側邊欄:使用自定義模板標簽
我們的博客側邊欄有四項內容:最新文章、歸檔、分類和標簽雲。這些內容相對比較固定,且在各個頁面都會顯示,如果像文章列表或者文章詳情一樣,從視圖函數中獲取然後傳遞給模板,則每個頁面對應的視圖函數里都要寫一段獲取這些內容的代碼,這會導致很多重覆代碼。更好的解決方案是直接在模板中獲取,為此,我們使用 Django 的一個新技術:自定義模板標簽來完成任務。
使用模板標簽的思路
我們前面已經接觸過一些 Django 內置的模板標簽,比如比較簡單的 {% static %} 模板標簽,這個標簽幫助我們在模板中引入靜態文件。還有比較複雜的如 {% for %} {% endfor%} 標簽。這裡 我們希望自己定義一個模板標簽,例如名為 get_recent_posts
的模板標簽,它可以這樣工作:我們只要在模板中寫入 {% get_recent_posts as recent_post_list %},那麼模板中就會有一個從資料庫獲取的最新文章列表,並通過 as 語句保存到 recent_post_list
模板變數里。這樣我們就可以通過 {% for %} {% endfor%} 模板標簽來迴圈這個變數,顯示最新文章列表了,這和我們在編寫博客首頁面視圖函數是類似的。首頁視圖函數中從資料庫獲取文章列表並保存到 post_list
變數,然後把這個 post_list
變數傳給模板,模板使用 for 模板標簽迴圈這個文章列表變數,從而展示一篇篇文章。這裡唯一的不同是我們從資料庫獲取文章列表的操作不是在視圖函數中進行,而是在模板中通過自定義的 {% get_recent_posts %} 模板標簽進行。
以上就是解決思路,但模板標簽不是我們隨意寫的,必須遵循 Django 的規範我們才能在 Django 的模板系統中使用自定義的模板標簽,下麵我們就依照這些規範來實現我們的需求。
模板標簽目錄結構
首先在我們的 blog 應用下創建一個 templatetags 文件夾。然後在這個文件夾下創建一個 __init__.py 文件,使這個文件夾成為一個 Python 包,之後在 templatetags\ 目錄下創建一個 blog_tags.py 文件,這個文件存放自定義的模板標簽代碼。
接下來就是編寫各個模板標簽的代碼了,自定義模板標簽代碼寫在 blog_tags.py 文件中。其實模板標簽本質上就是一個 Python 函數,因此按照 Python 函數的思路來編寫模板標簽的代碼就可以了
10.1.最新文章模板標簽
打開 blog_tags.py 文件,開始寫最新文章模板標簽。
blog/templatetags/blog_tags.py from ..models import Post def get_recent_posts(num=5): return Post.objects.all().order_by('-created_time')[:num]
這個函數的功能是獲取資料庫中前 num
篇文章,這裡 num
預設為 5。函數就這麼簡單,但目前它還只是一個純 Python 函數,Django 在模板中還不知道該如何使用它。為了能夠通過 {% get_recent_posts %} 的語法在模板中調用這個函數,必須按照 Django 的規定註冊這個函數為模板標簽,方法如下:
blog/templatetags/blog_tags.py from django import template from ..models import Post register = template.Library() @register.simple_tag def get_recent_posts(num=5): return Post.objects.all().order_by('-created_time')[:num]
這裡首先導入 template 這個模塊,然後實例化了一個 template.Library
類,並將函數 get_recent_posts
裝飾為 register.simple_tag
。這樣就可以在模板中使用語法 {% get_recent_posts %} 調用這個函數了。
10.2.歸檔模板標簽
和最新文章模板標簽一樣,先寫好函數,然後將函數註冊為模板標簽即可。
blog/templatetags/blog_tags.py @register.simple_tag def archives(): return Post.objects.dates('created_time', 'month', order='DESC')
這裡 dates
方法會返回一個列表,列表中的元素為每一篇文章(Post)的創建時間,且是 Python 的 date
對象,精確到月份,降序排列。接受的三個參數值表明瞭這些含義,一個是 created_time
,即 Post
的創建時間,month
是精度,order='DESC'
表明降序排列(即離當前越近的時間越排在前面)。例如我們寫了 3 篇文章,分別發佈於 2018 年 2 月 14 日、2018 年 3 月 14 日、2018 年 3 月 15 日,那麼 dates
函數將返回 2018 年 3 月 和 2018 年 2 月這樣一個時間列表,且降序排列,從而幫助我們實現按月歸檔的目的。
10.3.分類標簽模板
過程還是一樣,先寫好函數,然後將函數註冊為模板標簽。先導入Categor類
blog/templatetags/blog_tags.py from ..models import Post, Category @register.simple_tag def get_categories(): # 別忘了在頂部引入 Category 類 return Category.objects.all()
儘管側邊欄有 4 項內容(還有一個標簽雲),但是這裡我們只實現最新文章、歸檔和分類數據的顯示,還有一個標簽雲沒有實現。因為標簽雲的實現稍有一點不同
10.4.使用自定的模板標簽
打開 base.html,為了使用模板標簽,我們首先需要在模板中導入存放這些模板標簽的模塊,這裡是 blog_tags.py 模塊。當時我們為了使用 static 模板標簽時曾經導入過 {% load staticfiles %},這次在 {% load staticfiles %} 下再導入 blog_tags:
templates/base.html {% load staticfiles %} {% load blog_tags %} <!DOCTYPE html> <html> ... </html>
然後找到最新文章列表處,把裡面的列表修改一下:
templates/base.html <div class="widget widget-recent-posts"> <h3 class="widget-title">最新文章</h3> {% get_recent_posts as recent_post_list %} <ul> {% for post in recent_post_list %} <li> <a href="{{ post.get_absolute_url }}">{{ post.title }}</a> </li> {% empty %} 暫無文章! {% endfor %} </ul> </div>
這裡我們通過使用 get_recent_posts
模板標簽獲取到最新文章列表,然後我們通過 as 語法(Django 模板系統的語法)將獲取的文章列表保存進了 recent_post_list
模板變數中,之後就可以通過 for 迴圈來迴圈顯示文章列表數據了,這和我們在寫首頁視圖時是一樣的。
然後是歸檔部分:
templates/base.html <div class="widget widget-archives"> <h3 class="widget-title">歸檔</h3> {% archives as date_list %} <ul> {% for date in date_list %} <li> <a href="#">{{ date.year }} 年 {{ date.month }} 月</a> </li> {% empty %} 暫無歸檔! {% endfor %} </ul> </div>
同樣,這裡我們調用 archives
模板標簽自動獲取一個已發表文章的日期列表,精確到月份,降序排列,然後通過 as 語法將其保存在 date_list
模板變數里。由於日期列表中的元素為 Python 的 date
對象,因此可以通過其 year
和 month
屬性分別獲取年和月的信息,<a href="#">{{ date.year }} 年 {{ date.month }} 月</a>
反應了這個事實。
分類部分也一樣:
<div class="widget widget-category"> <h3 class="widget-title">分類</h3> {% get_categories as category_list %} <ul> {% for category in category_list %} <li> <a href="#">{{ category.name }} <span class="post-count">(13)</span></a> </li> {% empty %} 暫無分類! {% endfor %} </ul> </div>
<span class="post-count">(13)</span>
顯示的是該分類下的文章數目,這個特性會在接下來的教程中講解如何實現,目前暫時用占位數據代替吧。
現在運行開發伺服器,可以看到側邊欄顯示的數據已經不再是之前的占位數據,而是我們保存在資料庫中的數據了。
十一、分類與歸檔
側邊欄已經正確地顯示了最新文章列表、歸檔、分類等信息。現在來完善歸檔和分類功能,當用戶點擊歸檔下的某個日期或者分類下的某個分類時,跳轉到文章列表頁面,顯示該日期或者分類下的全部文章。
11.1.歸檔頁面
要顯示某個歸檔日期下的文章列表,思路和顯示主頁文章列表是一樣的,回顧一下主頁視圖的代碼:
blog/views.py def index(request): post_list = Post.objects.all().order_by('-created_time') return render(request, 'blog/index.html', {'post_list': post_list})
主頁視圖函數中我們通過 Post.objects.all()
獲取全部文章,而在我們的歸檔和分類視圖中,我們不再使用 all
方法獲取全部文章,而是使用 filter
來根據條件過濾。先來看歸檔視圖:
blog/views.py def archives(request, year, month): post_list = Post.objects.filter(created_time__year=year, created_time__month=month ).order_by('-created_time') return render(request, 'blog/index.html', {'post_list': post_list})
這裡我們使用了模型管理器(objects)的 filter
函數來過濾文章。由於是按照日期歸檔,因此這裡根據文章發表的年和月來過濾。具體來說,就是根據 created_time
的 year
和 month
屬性過濾,篩選出文章發表在對應的 year 年和 month 月的文章。註意這裡 created_time
是 Python 的 date
對象,其有一個 year
和 month
屬性,我們在 頁面側邊欄:使用自定義模板標簽 使用過這個屬性。Python 中類實例調用屬性的方法通常是 created_time.year
,但是由於這裡作為函數的參數列表,所以 Django 要求我們把點替換成了兩個下劃線,即 created_time__year
。同時和 index 視圖中一樣,我們對返回的文章列表進行了排序。此外由於歸檔的下的文章列表的顯示和首頁是一樣的,因此我們直接渲染了index.html 模板。
寫好視圖函數後就是配置好 URL:
blog/urls.py from django.conf.urls import url from . import views app_name = 'blog' urlpatterns = [ url(r'^$', views.index, name='index'), url(r'^post/(?P<pk>[0-9]+)/$', views.detail, name='detail'), url(r'^archives/(?P<year>[0-9]{4})/(?P<month>[0-9]{1,2})/$', views.archives, name='archives'), ]
這個歸檔視圖對應的 URL 的正則表達式和 detail
視圖函數對應的 URL 是類似的,這在之前我們講過。兩個括弧括起來的地方是兩個命名組參數,Django 會從用戶訪問的 URL 中自動提取這兩個參數的值,然後傳遞給其對應的視圖函數。例如如果用戶想查看 2018 年 3 月下的全部文章,他訪問 /archives/2018/3/,那麼 archives
視圖函數的實際調用為:archives(request, year=2018, month=3)
。
在模板找到歸檔列表部分的代碼,修改超鏈接的 href
屬性,讓用戶點擊超鏈接後跳轉到文章歸檔頁面:
templates/base.html {% for date in date_list %} <li> <a href="{% url 'blog:archives' date.year date.month %}"> {{ date.year }} 年 {{ date.month }} 月 </a> </li> {% endfor %}
這裡 {% url %} 這個模板標簽的作用是解析視圖函數 blog:archives
對應的 URL 模式,並把 URL 模式中的年和月替換成 date.year
,date.month
的值。例如 blog:archives 表示 blog 應用下的 archives 函數,這個函數對應的 URL 模式為 ^archives/(?P<year>[0-9]{4})/(?P<month>[0-9]{1,2})/$
,假設 date.year=2018
,date.month=5
,那麼 {% url 'blog:archives' date.year date.month %} 模板標簽返回的值為/archives/2018/5/。
為什麼要使用 {% url %} 模板標簽呢?事實上,我們把超鏈接的 href 屬性設置為 /archives/{{ date.year }}/{{ date.month }}/
同樣可以達到目的,但是這種寫法是硬編碼的。雖然現在 blog:archives 視圖函數對應的 URL 模式是這種形式,但是如果哪天這個模式改變了呢?如果使用了硬編碼的寫法,那你需要把每一處 /archives/{{ date.year }}/{{ date.month }}/
修改為新的模式。但如果使用了 {% url %} 模板標簽,則不用做任何修改。
測試一下,點擊側邊欄歸檔的日期,跳轉到歸檔頁面,發現並沒有顯示歸檔下的文章列表,因為還要改一下時區:
首先安裝pytz模塊(django處理時區用的,安裝即可,無需其它操作),然後更改settings設置
#settings.py LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_L10N = True USE_TZ = False
再次測試,發現可以顯示歸檔下的文章列表了。
11.2.分類頁面
同樣的寫好分類頁面的視圖函數:
blog/views.py import markdown from django.shortcuts import render, get_object_or_404 # 引入 Category 類 from .models import Post, Category def category(request, pk): # 記得在開始部分導入 Category 類 cate = get_object_or_404(Category, pk=pk) post_list = Post.objects.filter(category=cate).order_by('-created_time') return render(request, 'blog/index.html', context={'post_list': post_list})
這裡我們首先根據傳入的 pk 值(也就是被訪問的分類的 id 值)從資料庫中獲取到這個分類。get_object_or_404
函數和 detail 視圖中一樣,其作用是如果用戶訪問的分類不存在,則返回一個 404 錯誤頁面以提示用戶訪問的資源不存在。然後我們通過 filter
函數過濾出了該分類下的全部文章。同樣也和首頁視圖中一樣對返回的文章列表進行了排序。
URL 配置如下:
blog/urls.py from django.conf.urls import url from . import views app_name = 'blog' urlpatterns = [ url(r'^$', views.index, name='index'), url(r'^post/(?P<pk>[0-9]+)/$', views.detail, name='detail'), url(r'^archives/(?P<year>[0-9]{4})/(?P<month>[0-9]{1,2})/$', views.archives, name='archives'), url(r'^category/(?P<pk>[0-9]+)/$', views.category, name='category'), ]
修改相應模板:
templates/base.html {% for category in category_list %} <li> <a href="{% url 'blog:category' category.pk %}">{{ category.name }}</a> </li> {% endfor %}
同樣,{% url %} 模板標簽的用法和寫歸檔頁面時的用法是一樣的。現在嘗試點擊相應的鏈接,就可以跳轉到歸檔或者分類頁面了。
十二、評論
相對來說,評論其實是另外一個比較獨立的功能。Django 提倡,如果功能相對比較獨立的話,最好是創建一個應用,把相應的功能代碼寫到這個應用里。我們的第一個應用叫 blog,它裡面放了展示博客文章列表和細節等相關功能的代碼。而這裡我們再創建一個應用,名為 comments,這裡面將存放和評論功能相關的代碼。
python manage.py startapp comments
創建新的應用後一定要記得在 settings.py 里註冊這個應用,Django 才知道這是一個應用
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'blog', 'comments' ]
12.1.設計評論的資料庫模型
用戶評論的數據必須被存儲到資料庫里,以便其他用戶訪問時 Django 能從資料庫取回這些數據然後展示給訪問的用戶,因此我們需要為評論設計資料庫模型,這和設計文章、分類、標簽的資料庫模型是一樣的,評論模型設計如下(評論模型的代碼寫在 comment\models.py 里):
comments/models.py from django.db import models class Comment(models.Model): name = models.CharField(max_length=100) email = models.EmailField(max_length=255) url = models.URLField(blank=True) text = models.TextField() created_time = models.DateTimeField(auto_now_add=True) post = models.ForeignKey('blog.Post') def __str__(self): return self.text[:20]
這裡我們會保存評論用戶的 name(名字)、email(郵箱)、url(個人網站),用戶發表的內容將存放在 text 欄位里,created_time 記錄評論時間。最後,這個評論是關聯到某篇文章(Post)的,由於一個評論只能屬於一篇文章,一篇文章可以有多個評論,是一對多的關係,因此這裡我們使用了 ForeignKey。關於 ForeKey 我們前面已有介紹,這裡不再贅述。
同時註意我們為 DateTimeField
傳遞了一個 auto_now_add=True
的參數值。auto_now_add
的作用是,當評論數據保存到資料庫時,自動把 created_time
的值指定為當前時間。created_time
記錄用戶發表評論的時間,我們肯定不希望用戶在發表評論時還得自己手動填寫評論發表時間,這個時間應該自動生成。
創建了資料庫模型就要遷移資料庫,分別運行下麵兩條命令:
python manage.py makemigrations
python manage.py migrate
12.2.評論表單設計
下麵開始編寫評論表單代碼。在 comments\ 目錄下(和 models.py 同級)新建一個 forms.py 文件,用來存放表單代碼,我們的表單代碼如下:
comments/forms.py from django import forms from .models import Comment class CommentForm(forms.ModelForm): class Meta: model = Comment fields = ['name', 'email', 'url', 'text']
要使用 Django 的表單功能,我們首先導入 forms 模塊。Django 的表單類必須繼承自 forms.Form
類或者 forms.ModelForm
類。如果表單對應有一個資料庫模型(例如這裡的評論表單對應著評論模型),那麼使用 ModelForm
類會簡單很多,這是 Django 為我們提供的方便。之後我們在表單的內部類 Meta
里指定一些和表單相關的東西。model = Comment
表明這個表單對應的資料庫模型是 Comment
類。fields = ['name', 'email', 'url', 'text']
指定了表單需要顯示的欄位,這裡我們指定了 name、email、url、text 需要顯示。
12.3.評論視圖函數
當用戶提交表單中的數據後,Django 需要調用相應的視圖函數來處理這些數據,下麵開始寫我們視圖函數處理邏輯:
comments/views.py from django.shortcuts import render, get_object_or_404, redirect from blog.models import Post from .models import Comment from .forms import CommentForm def post_comment(request, post_pk): # 先獲取被評論的文章,因為後面需要把評論和被評論的文章關聯起來。 # 這裡我們使用了 Django 提供的一個快捷函數 get_object_or_404, # 這個函數的作用是當獲取的文章(Post)存在時,則獲取;否則返回 404 頁面給用戶。 post = get_object_or_404(Post, pk=post_pk) # HTTP 請求有 get 和 post 兩種,一般用戶通過表單提交數據都是通過 post 請求, # 因此只有當用戶的請求為 post 時才需要處理表單數據。 if request.method == 'POST': # 用戶提交的數據存在 request.POST 中,這是一個類字典對象。 # 我們利用這些數據構造了 CommentForm 的實例,這樣 Django 的表單就生成了。 form = CommentForm(request.POST) # 當調用 form.is_valid() 方法時,Django 自動幫我們檢查表單的數據是否符合格式要求。 if form.is_valid(): # 檢查到數據是合法的,調用表單的 save 方法保存數據到資料庫, # commit=False 的作用是僅僅利用表單的數據生成 Comment 模型類的實例,但還不保存評論數據到資料庫。 comment = form.save(commit=False) # 將評論和被評論的文章關聯起來。 comment.post = post # 最終將評論數據保存進資料庫,調用模型實例的 save 方法 comment.save() # 重定向到 post 的詳情頁,實際上當 redirect 函數接收一個模型的實例時,它會調用這個模型實例的 get_absolute_url 方法, # 然後重定向到 get_absolute_url 方法返回的 URL。 return redirect(post) else: # 檢查到數據不合法,重新渲染詳情頁,並且渲染表單的錯誤。 # 因此我們傳了三個模板變數給 detail.html, # 一個是文章(Post),一個是評論列表,一個是表單 form # 註意這裡我們用到了 post.comment_set.all() 方法, # 這個用法有點類似於 Post.objects.all() # 其作用是獲取這篇 post 下的的全部評論, # 因為 Post 和 Comment 是 ForeignKey 關聯的, # 因此使用 post.comment_set.all() 反向查詢全部評論。 # 具體請看下麵的講解。 comment_list = post.comment_set.all() context = {'post': post, 'form': form, 'comment_list': comment_list } return render(request, 'blog/detail.html', context=context) # 不是 post 請求,說明用戶沒有提交數據,重定向到文章詳情頁。 return redirect(post)
這個評論視圖相比之前的一些視圖複雜了很多,主要是處理評論的過程更加複雜。具體過程在代碼中已有詳細註釋,這裡僅就視圖中出現了一些新的知識點進行講解。
首先我們使用了 redirect
函數。這個函數位於 django.shortcuts 模塊中,它的作用是對 HTTP 請求進行重定向(即用戶訪問的是某個 URL,但由於某些原因,伺服器會將用戶重定向到另外的 URL)。redirect
既可以接收一個 URL 作為參數,也可以接收一個模型的實例作為參數(例如這裡的 post)。如果接收一個模型的實例,那麼這個實例必須實現了 get_absolute_url
方法,這樣 redirect
會根據 get_absolute_url
方法返回的 URL 值進行重定向。
另外我們使用了 post.comment_set.all()
來獲取 post
對應的全部評論。 Comment
和Post
是通過 ForeignKey
關聯的,回顧一下我們當初獲取某個分類 cate
下的全部文章時的代碼:Post.objects.filter(category=cate)
。這裡 post.comment_set.all()
也等價於 Comment.objects.filter(post=post)
,即根據 post
來過濾該 post
下的全部評論。但既然我們已經有了一個 Post
模型的實例 post
(它對應的是 Post
在資料庫中的一條記錄),那麼獲取和 post
關聯的評論列表有一個簡單方法,即調用它的 xxx_set 屬性來獲取一個類似於 objects 的模型管理器,然後調用其 all
方法來返回這個 post
關聯的全部評論。 其中 xxx_set 中的 xxx 為關聯模型的類名(小寫)。例如 Post.objects.filter(category=cate)
也可以等價寫為 cate.post_set.all()
。
12.4.綁定url
視圖函數需要和 URL 綁定,這裡我們在 comment 應用中再建一個 urls.py 文件,寫上 URL 模式:
comments/urls.py from django.conf.urls import url from . import views app_name = 'comments' urlpatterns = [ url(r'^comment/post/(?P<post_pk>[0-9]+)/$', views.post_comment, name='post_comment'), ]
別忘了給這個評論的 URL 模式規定命名空間,即 app_name = 'comments'
。
最後要在項目的 blogprokect\ 目錄的 urls.py 里包含 comments\urls.py 這個文件:
blogproject/urls.py urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'', include('blog.urls')), url(r'', include('comments.urls')), ]
12.5..更新文章詳情頁面的視圖函數
我們可以看到評論表單和評論列表是位於文章詳情頁面的,處理文章詳情頁面的視圖函數是 detail,相應地需要更新 detail,讓它生成表單和從資料庫獲取文章對應的評論列表數據,然後傳遞給模板顯示:
blog/views.py import markdown from django.shortcuts import render, get_object_or_404 + from comments.forms import CommentForm from .models import Post, Category def detail(request, pk): post = get_object_or_404(Post, pk=pk) post.body = markdown.markdown(post.body, extensions=[ 'markdown.extensions.extra', 'markdown.extensions.codehilite', 'markdown.extensions.toc', ]) # 記得在頂部導入 CommentForm form = CommentForm() # 獲取這篇 post 下的全部評論 comment_list = post.comment_set.all() # 將文章、表單、以及文章下的評論列表作為模板變數傳給 detail.html 模板,以便渲染相應數據。 context = {'post': post, 'form': form, 'comment_list': comment_list } return render(request, 'blog/detail.html', context=context)
12.6.在前段渲染頁面
使用 Django 表單的一個好處就是 Django 能幫我們自動渲染表單。我們在表單的視圖函數里傳遞了一個 form
變數給模板,這個變數就包含了自動生成 HTML 表單的全部數據。在 detail.html 中通過 form 來自動生成表單。刪掉原來用於占位的 HTML 評論表單代碼,即下麵這段代碼:
<form action="#" method="post" class="comment-form"> <div class="row"> <div class="col-md-4"> <label for="id_name">名字:</label> <input type="text" id="id_name" name="name" required> </div> ... </div> <!-- row --> </form>
替換成如下的代碼:
<form action="{% url 'comments:post_comment' post.pk %}" method="post" class="comment-form"> {% csrf_token %} <div class="row"> <div class="col-md-4"> <label for="{{ form.name.id_for_label }}">名字:</label> {{ form.name }} {{ form.name.errors }} </div> <div class="col-md-4"> <label for="{{ form.email.id_for_label }}">郵箱:</label> {{ form.email }} {{ form.email.errors }} </div> <div class="col-md-4"> <label for="{{ form.url.id_for_label }}">URL:</label> {{ form.url }} {{ form.url.errors }} </div> <div class="col-md-12"> <label for="{{ form.text.id_for_label }}">評論:</label> {{ form.text }} {{ form.text.errors }} <button type="submit" class="comment-btn">發表</button> </div> </div> <!-- row --> </form>
{{ form.name }}、{{ form.email }}、{{ form.url }} 等將自動渲染成表單控制項,例如 <input>
控制項。
{{ form.name.errors }}、{{ form.email.errors }} 等將渲染表單對應欄位的錯誤(如果有的話),例如用戶 email 格式填錯了,那麼 Django 會檢查用戶提交的 email 的格式,然後將格式錯誤信息保存到 errors 中,模板便將錯誤信息渲染顯示。
12.7.顯示評論內容
在 detail 視圖函數我們獲取了全部評論數據,並通過 comment_list
傳遞給了模板。和處理 index 頁面的文章列表方式是一樣的,我們在模板中通過 {% for %} 模板標簽來迴圈顯示文章對應的全部評論內容。
刪掉占位用的評論內容的 HTML 代碼,即如下的代碼:
<ul class="comment-list list-unstyled"> <li class="comment-item"> <span class="nickname">追夢人物</span> <time class="submit-date">2017年3月12日 14:56</time> <div class="text"> 文章觀點又有道理又符合人性,這才是真正為了表達觀點而寫,不是為了迎合某某知名人士粉絲而寫。我覺得如果瓊瑤是前妻,生了三孩子後被一不知名的女人挖了牆角,我不信誰會說那個女人是追求真愛,說同情瓊瑤罵小三的女人都是弱者。 </div> </li> ... </ul>
替換成如下的代碼:
<ul class="comment-list list-unstyled"> {% for comment in comment_list %} <li class="comment-item"> <span class="nickname">{{ comment.name }}</span> <time class="submit-date">{{ comment.created_time }}</time> <div class="text"> {{ comment.text }} </div> </li> {% empty %} 暫無評論 {% endfor %} </ul>
接下來嘗試在詳情頁下的評論表單提交一些評論數據,可以看到詳情頁的評論列表處渲染了你提交的評論數據。
12.8.完善跳轉鏈接
導航欄有一個