第 14 篇:交流的橋樑「評論功能」—— HelloDjango 系列教程

做者:HelloGitHub- 追夢人物

文中涉及的示例代碼,已同步更新到 HelloGitHub-Team 倉庫html

截止到目前爲止咱們的 django blog 文章展現部分,已經實現的「八九不離十」了。你覺得本系列文章就要結束了嗎?不可以!新的征程纔剛剛開始,HelloDjango 系列文章剛剛過半,後面的文章你將接觸更多博客系統的細節。向着一個小而全的博客系統前進、前進、前進,你定會收穫頗多。前端

今天咱們就來開啓博客的評論功能,建起和讀者的溝通橋樑。python

建立評論應用

相對來講,評論是另一個比較獨立的功能。Django 提倡,若是功能相對比較獨立的話,最好是建立一個應用,把相應的功能代碼組織到這個應用裏。咱們的第一個應用叫 blog,它裏面放了展現博客文章列表和詳情等相關功能的代碼。而這裏咱們再建立一個應用,名爲 comments 這裏面將存放和評論功能相關的代碼。首先進入到項目根目錄,而後輸入以下命令建立一個新的應用:git

> pipenv run python manage.py startapp comments
複製代碼

能夠看到生成的 comments 應用目錄結構和 blog 應用的目錄是相似的(關於建立應用以及應用的目錄結構在 "空空如也"的博客應用 中已經有過詳細介紹)。github

建立新的應用後必定要記得在 settings.py 裏註冊這個應用,django 才知道這是一個應用。shell

blogproject/settings.py

...
INSTALLED_APPS = [
    ...
    'blog.apps.BlogConfig',  # 註冊 blog 應用
    'comments.apps.CommentsConfig',  # 註冊 comments 應用
]v
...
複製代碼

注意這裏註冊的是 CommentsConfig 類,在 博客從「裸奔」到「有皮膚」 中曾經講過如何對應用作一些初始化配置,例如讓 blog 應用在 django 的 admin 後臺顯示中文名字。這裏也對評論應用作相似的配置:數據庫

comments/app.py

from django.apps import AppConfig


class CommentsConfig(AppConfig):
    name = 'comments'
    verbose_name = '評論'
複製代碼

設計評論的數據庫模型

用戶評論的數據必須被存儲到數據庫裏,以便其餘用戶訪問時 django 能從數據庫取回這些數據而後展現給訪問的用戶,所以咱們須要爲評論設計數據庫模型,這和設計文章、分類、標籤的數據庫模型是同樣的,若是你忘了怎麼作,再回顧一下 建立 Django 博客的數據庫模型 中的作法。咱們的評論模型設計以下(評論模型的代碼寫在 comments\models.py 裏):django

comments/models.py

from django.db import models
from django.utils import timezone


class Comment(models.Model):
    name = models.CharField('名字', max_length=50)
    email = models.EmailField('郵箱')
    url = models.URLField('網址', blank=True)
    text = models.TextField('內容')
    created_time = models.DateTimeField('建立時間', default=timezone.now)
    post = models.ForeignKey('blog.Post', verbose_name='文章', on_delete=models.CASCADE)

    class Meta:
        verbose_name = '評論'
        verbose_name_plural = verbose_name

    def __str__(self):
        return '{}: {}'.format(self.name, self.text[:20])
複製代碼

評論會保存評論用戶的 name(名字)、email(郵箱)、url(我的網站,能夠爲空),用戶發表的內容將存放在 text 字段裏,created_time 記錄評論時間。最後,這個評論是關聯到某篇文章(Post)的,因爲一個評論只能屬於一篇文章,一篇文章能夠有多個評論,是一對多的關係,所以這裏咱們使用了 ForeignKey。關於 ForeignKey 咱們前面已有介紹,這裏再也不贅述。編程

此外,在 博客從「裸奔」到「有皮膚」 中提過,全部模型的字段都接受一個 verbose_name 參數(大部分是第一個位置參數),django 在根據模型的定義自動生成表單時,會使用這個參數的值做爲表單字段的 label,咱們在後面定義的評論表單時會進一步看到其做用。bootstrap

建立了數據庫模型就要遷移數據庫,遷移數據庫的命令也在前面講過。在項目根目錄下分別運行下面兩條命令:

> pipenv run python manage.py makemigrations
> pipenv run python manage.py migrate
複製代碼

註冊評論模型到 admin

既然已經建立了模型,咱們就能夠將它註冊到 django admin 後臺,方便管理員用戶對評論進行管理,如何註冊 admin 以及美化在 博客從「裸奔」到「有皮膚」 有過詳細介紹,這裏給出相關代碼:

comments/admin.py

from django.contrib import admin
from .models import Comment


class CommentAdmin(admin.ModelAdmin):
    list_display = ['name', 'email', 'url', 'post', 'created_time']
    fields = ['name', 'email', 'url', 'text', 'post']


admin.site.register(Comment, CommentAdmin)
複製代碼

設計評論表單

這一節咱們將學習一個全新的 django 知識:表單。那麼什麼是表單呢?基本的 HTML 知識告訴咱們,在 HTML 文檔中這樣的代碼表示一個表單:

<form action="" method="post">
  <input type="text" name="username" />
  <input type="password" name="password" />
  <input type="submit" value="login" />
</form>
複製代碼

爲何須要表單呢?表單是用來收集並向服務器提交用戶輸入的數據的。考慮用戶在咱們博客網站上發表評論的過程。當用戶想要發表評論時,他找到咱們給他展現的一個評論表單(咱們已經看到在文章詳情頁的底部就有一個評論表單,你將看到表單呈現給咱們的樣子),而後根據表單的要求填寫相應的數據。以後用戶點擊評論按鈕,這些數據就會發送給某個 URL。咱們知道每個 URL 對應着一個 django 的視圖函數,因而 django 調用這個視圖函數,咱們在視圖函數中寫上處理用戶經過表單提交上來的數據的代碼,好比驗證數據的合法性而且保存數據到數據庫中,那麼用戶的評論就被 django 處理了。若是經過表單提交的數據存在錯誤,那麼咱們把錯誤信息返回給用戶,並在前端從新渲染表單,要求用戶根據錯誤信息修正表單中不符合格式的數據,再從新提交。

django 的表單功能就是幫咱們完成上述所說的表單處理邏輯,表單對 django 來講是一個內容豐富的話題,很難經過教程中的這麼一個例子涵蓋其所有用法。所以咱們強烈建議你在完成本教程後接下來的學習中仔細閱讀 django 官方文檔關於 表單 的介紹,由於表單在 Web 開發中會常常遇到。

下面開始編寫評論表單代碼。在 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 須要顯示。

關於表單進一步的解釋

django 爲何要給咱們提供一個表單類呢?爲了便於理解,咱們能夠把表單和前面講過的 django ORM 系統作類比。回想一下,咱們使用數據庫保存建立的博客文章,可是從頭至尾沒有寫過任何和數據庫有關的代碼(要知道數據庫自身也有一門數據庫語言),這是由於 django 的 ORM 系統內部幫咱們作了一些事情。咱們遵循 django 的規範寫的一些 Python 代碼,例如建立 Post、Category 類,而後經過運行數據庫遷移命令將這些代碼反應到數據庫。

django 的表單和這個思想相似,正常的前端表單代碼應該是和本文開頭所說起的那樣的 HTML 代碼,可是咱們目前並無寫這些代碼,而是寫了一個 CommentForm 這個 Python 類。經過調用這個類的一些方法和屬性,django 將自動爲咱們建立常規的表單代碼,接下來的教程咱們就會看到具體是怎麼作的。

展現評論表單

表單類已經定義完畢,如今的任務是在文章的詳情頁下方將這個表單展示給用戶,用戶即可以經過這個表單填寫評論數據,從而發表評論。

那麼怎麼展示一個表單呢?django 會根據表單類的定義自動生成表單的 HTML 代碼,咱們要作的就是實例化這個表單類,而後將表單的實例傳給模板,讓 django 的模板引擎來渲染這個表單。

那怎麼將表單類的實例傳給模板呢?由於表單出如今文章詳情頁,一種想法是修改文章詳情頁 detail 視圖函數,在這個視圖中實例化一個表單,而後傳遞給模板。然而這樣作的一個缺點就是須要修改 detail 視圖函數的代碼,並且 detail 視圖函數的做用主要就是處理文章詳情,一個視圖函數最好不要讓它作太多雜七雜八的事情。另一種想法是使用自定義的模板標籤,咱們在 頁面側邊欄:使用自定義模板標籤 中詳細介紹過如何自定義模板標籤來渲染一個局部的 HTML 頁面,這裏咱們使用自定義模板標籤的方法,來渲染表單頁面。

和 blog 應用中定義模板標籤的老套路同樣,首先創建評論應用模板標籤的文件結構,在 comments\ 文件夾下新建一個 templatetags 文件夾,而後建立 __init__.py 文件使其成爲一個包,再建立一個 comments_extras.py 文件用於存放模板標籤的代碼,文件結構以下:

...
blog\
comments\
	templatetags\
		__init__.py
		comments_extras.py
...
複製代碼

而後咱們定義一個 inclusion_tag 類型的模板標籤,用於渲染評論表單,關於如何定義模板標籤,在 頁面側邊欄:使用自定義模板標籤 中已經有詳細介紹,這裏再也不贅述。

from django import template
from ..forms import CommentForm

register = template.Library()


@register.inclusion_tag('comments/inclusions/_form.html', takes_context=True)
def show_comment_form(context, post, form=None):
    if form is None:
        form = CommentForm()
    return {
        'form': form,
        'post': post,
    }
複製代碼

從定義能夠看到,show_comment_form 模板標籤使用時會接受一個 post(文章 Post 模型的實例)做爲參數,同時也可能傳入一個評論表單 CommentForm 的實例 form,若是沒有接受到評論表單參數,模板標籤就會新建立一個 CommentForm 的實例(一個沒有綁定任何數據的空表單)傳給模板,不然就直接將接受到的評論表單實例直接傳給模板,這主要是爲了複用已有的評論表單實例(後面會看到其用法)。

而後在 templates/comments/inclusions 目錄下(沒有就新建)新建一個 _form.html 模板,寫上代碼:

<form action="{% url 'comments: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 }}">{{ form.name.label }}:</label>
      {{ form.name }}
      {{ form.name.errors }}
    </div>
    <div class="col-md-4">
      <label for="{{ form.email.id_for_label }}">{{ form.email.label }}:</label>
      {{ form.email }}
      {{ form.email.errors }}
    </div>
    <div class="col-md-4">
      <label for="{{ form.url.id_for_label }}">{{ form.url.label }}:</label>
      {{ form.url }}
      {{ form.url.errors }}
    </div>
    <div class="col-md-12">
      <label for="{{ form.text.id_for_label }}">{{ form.text.label }}:</label>
      {{ form.text }}
      {{ form.text.errors }}
      <button type="submit" class="comment-btn">發表</button>
    </div>
  </div>    <!-- row -->
</form>
複製代碼

這個表單的模板有點複雜,一一講解一下。

首先 HTML 的 form 標籤有 2 個重要的屬性,actionmethodaction 指定表單內容提交的地址,這裏咱們提交給 comments:comment 視圖函數對應的 URL(後面會建立這個視圖函數並綁定對應的 URL),模板標籤 url 的用法在 分類、歸檔和標籤頁 教程中有詳細介紹。method 指定提交表單時的 HTTP 請求類型,通常表單提交都是使用 POST。

而後咱們看到 {% csrf_token %},這個模板標籤在表單渲染時會自動渲染爲一個隱藏類型的 HTML input 控件,其值爲一個隨機字符串,做用主要是爲了防禦 CSRF(跨站請求僞造)攻擊。{% csrf_token %} 在模板中渲染出來的內容大概以下所示:

<input type="hidden" name="csrfmiddlewaretoken" value="KH9QLnpQPv2IBcv3oLsksJXdcGvKSnC8t0mTfRSeNIlk5T1G1MBEIwVhK4eh6gIZ">
複製代碼

CSRF 攻擊是一種常見的 Web 攻擊手段。攻擊者利用用戶存儲在瀏覽器中的 cookie,向目標網站發送 HTTP 請求,這樣在目標網站看來,請求來自於用戶,而實際發送請求的人倒是攻擊者。例如假設咱們的博客支持登陸功能(目前沒有),並使用 cookie(或者 session)記錄用戶的登陸狀態,且評論表單沒有 csrf token 防禦。用戶登陸了咱們的博客後,又去訪問了一個小電影網站,小電影網站有一段惡意 JavaScript 腳本,它讀取用戶的 cookie,並構造了評論表單的數據,而後腳本使用這個 cookie 向咱們的博客網站發送一條 POST 請求,django 就會認爲這是來自該用戶的評論發佈請求,便會在後臺建立一個該用戶的評論,而這個用戶全程一臉懵逼。

CSRF 的一個防範措施是,對全部訪問網站的用戶頒發一個令牌(token),對於敏感的 HTTP 請求,後臺會校驗此令牌,確保令牌的確是網站頒發給指定用戶的。所以,當用戶訪問別的網站時,雖然攻擊者能夠拿到用戶的 cookie,可是沒法取得證實身份的令牌,所以發過來的請求便不會被受理。

以上是對 CSRF 攻擊和防禦措施的一個簡單介紹,更加詳細的講解請使用搜索引擎搜索相關資料。

show_comment_form 模板標籤給模板傳遞了一個模板變量 form,它是 CommentForm 的一個實例,表單的字段 {{ form.name }}{{ form.email }}{{ form.url }} 等將自動渲染成表單控件,例如 <input> 控件。

注意到表單的定義中並無定義 nameemailurl 等屬性,那它們是哪裏來的呢?看到 CommentFormMeta 下的 fields,django 會自動將 fields 中聲明的模型字段設置爲表單的屬性。

{{ form.name.errors }}{{ form.email.errors }} 等將渲染表單對應字段的錯誤(若是有的話),例如用戶 email 格式填錯了,那麼 django 會檢查用戶提交的 email 的格式,而後將格式錯誤信息保存到 errors 中,模板便將錯誤信息渲染顯示。

{{ form.xxx.label }} 用來獲取表單的 label,以前說過,django 根據表單對應的模型中字段的 verbose_name 參數生成。

而後咱們就能夠在 detail.html 中使用這個模板標籤來渲染表單了,注意在使用前記得先 {% load comment_extras %} 這個模塊。並且爲了不可能的報錯,最好重啓一下開發服務器

{% extends 'base.html' %}
{% load comment_extras %}
...

<h3>發表評論</h3>
{% show_comment_form post %}
複製代碼

這裏當用戶訪問文章詳情頁面時,咱們給他展現一個空表單,因此這裏只傳入了 post 參數須要的值,而沒有傳入 form 參數所需的值。能夠看到表單渲染出來的結果了:

評論視圖函數

當用戶提交表單中的數據後,django 須要調用相應的視圖函數來處理這些數據,下面開始寫咱們視圖函數處理邏輯:

from blog.models import Post
from django.shortcuts import get_object_or_404, redirect, render
from django.views.decorators.http import require_POST

from .forms import CommentForm


@require_POST
def comment(request, post_pk):
    # 先獲取被評論的文章,由於後面須要把評論和被評論的文章關聯起來。
    # 這裏咱們使用了 django 提供的一個快捷函數 get_object_or_404,
    # 這個函數的做用是當獲取的文章(Post)存在時,則獲取;不然返回 404 頁面給用戶。
    post = get_object_or_404(Post, pk=post_pk)

    # django 將用戶提交的數據封裝在 request.POST 中,這是一個類字典對象。
    # 咱們利用這些數據構造了 CommentForm 的實例,這樣就生成了一個綁定了用戶提交數據的表單。
    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)

    # 檢查到數據不合法,咱們渲染一個預覽頁面,用於展現表單的錯誤。
    # 注意這裏被評論的文章 post 也傳給了模板,由於咱們須要根據 post 來生成表單的提交地址。
    context = {
        'post': post,
        'form': form,
    }
    return render(request, 'comments/preview.html', context=context)
複製代碼

這個評論視圖相比以前的一些視圖複雜了不少,主要是處理評論的過程更加複雜。具體過程在代碼中已有詳細註釋,這裏僅就視圖中出現了一些新的知識點進行講解。

首先視圖函數被 require_POST 裝飾器裝飾,從裝飾器的名字就能夠看出,其做用是限制這個視圖只能經過 POST 請求觸發,由於建立評論須要用戶經過表單提交的數據,而提交表單一般都是限定爲 POST 請求,這樣更加安全。

另外咱們使用了 redirect 快捷函數。這個函數位於 django.shortcuts 模塊中,它的做用是對 HTTP 請求進行重定向(即用戶訪問的是某個 URL,但因爲某些緣由,服務器會將用戶重定向到另外的 URL)。redirect 既能夠接收一個 URL 做爲參數,也能夠接收一個模型的實例做爲參數(例如這裏的 post)。若是接收一個模型的實例,那麼這個實例必須實現了 get_absolute_url 方法,這樣 redirect 會根據 get_absolute_url 方法返回的 URL 值進行重定向。

若是用戶提交的數據合法,咱們就將評論數據保存到數據庫,不然說明用戶提交的表單包含錯誤,咱們將渲染一個 preview.html 頁面,來展現表單中的錯誤,以便用戶修改後從新提交。preview.html 的代碼以下:

{% extends 'base.html' %}
{% load comment_extras %}

{% block main %}
  {% show_comment_form post form %}
{% endblock main %}
複製代碼

這裏仍是使用 show_comment_form 模板標籤來展現一個表單,然而不一樣的是,這裏咱們傳入由視圖函數 comment 傳來的綁定了用戶提交的數據的表單實例 form,而不是渲染一個空表單。由於視圖函數 comment 中的表單實例是綁定了用戶提交的評論數據,以及對數據進行過合法性校驗的表單,所以當 django 渲染這個表單時,會連帶渲染用戶已經填寫的表單數據以及數據不合法的錯誤提示信息,而不是一個空的表單了。例以下圖,咱們提交的數據中 email 格式不合法,表單校驗了數據格式,而後渲染錯誤提示:

綁定 URL

視圖函數須要和 URL 綁定,這裏咱們在 comment 應用中再建一個 urls.py 文件,寫上 URL 模式:

from django.urls import path

from . import views

app_name = 'comments'
urlpatterns = [
    path('comment/<int:post_pk>', views.comment, name='comment'),
]
複製代碼

別忘了給這個評論的 URL 模式規定命名空間,即 app_name = 'comments'

最後要在項目的 blogproject\ 目錄的 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')),
]
複製代碼

能夠測試一下提交評論的功能了,首先嚐試輸入非法格式的數據,例如將郵箱輸入爲 xxx@xxx,那麼評論視圖在校驗表單數據合法性時,發現郵箱格式不符,就會渲染 preview 頁面,展現表單中的錯誤,將郵箱修改成正確的格式後,再次點擊發表,頁面就跳轉到了被評論文章的詳情頁,說明視圖正確執行了保存表單數據到數據庫的邏輯。

不過這裏有一點很差的地方就是,評論成功後頁面直接跳轉到了被評論文章的詳情頁,沒有任何提示,用戶也不知道評論究竟有沒有真的成功。這裏咱們使用 django 自帶的 messages 應用來給用戶發送評論成功或者失敗的消息。

發送評論消息

django 默認已經爲咱們作好了 messages 的相關配置,直接用便可。

兩個地方須要發送消息,第一個是當評論成功,即評論數據成功保存到數據庫後,所以在 comment 視圖中加一句。

from django.contrib import messages

if form.is_valid():
    ...
    # 最終將評論數據保存進數據庫,調用模型實例的 save 方法
    comment.save()

    messages.add_message(request, messages.SUCCESS, '評論發表成功!', extra_tags='success')
    return redirect(post)
複製代碼

這裏導入 django 的 messages 模塊,使用 add_message 方法增長了一條消息,消息的第一個參數是當前請求,由於當前請求攜帶用戶的 cookie,django 默認將詳細存儲在用戶的 cookie 中。第二個參數是消息級別,評論發表成功的消息設置爲 messages.SUCCESS,這是 django 已經默認定義好的一個整數,消息級別也能夠本身定義。緊接着傳入消息的內容,最後 extra_tags 給這條消息打上額外的標籤,標籤值能夠在展現消息時使用,好比這裏咱們會把這個值用在模板中的 HTML 標籤的 class 屬性,增長樣式。

一樣的,若是評論失敗了,也發送一條消息:

# 檢查到數據不合法,咱們渲染一個預覽頁面,用於展現表單的錯誤。
# 注意這裏被評論的文章 post 也傳給了模板,由於咱們須要根據 post 來生成表單的提交地址。
context = {
    'post': post,
    'form': form,
}
messages.add_message(request, messages.ERROR, '評論發表失敗!請修改表單中的錯誤後從新提交。', extra_tags='danger')
複製代碼

發送的消息被緩存在 cookie 中,而後咱們在模板中獲取顯示便可。顯示消息比較好的地方是在導航條的下面,咱們在模板 base.html 的導航條代碼下增長以下代碼:

<header>
  ...
</header>
{% if messages %}
    {% for message in messages %}
      <div class="alert alert-{{ message.tags }} alert-dismissible" role="alert">
        <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
        {{ message }}
      </div>
    {% endfor %}
{% endif %}
複製代碼

這裏 django 會經過全局上下文自動把 messages 變量傳給模板,這個變量裏存儲咱們發送的消息內容,而後就是循環顯示消息了。這裏咱們使用了 bootstrap 的一個 alert 組件,爲其設置不一樣的 class 會顯示不一樣的顏色,因此以前添加消息時傳入的 extra_tags 就派上了用場。好比這裏 alert-{{ message.tags }},當傳入的是 success 時,類名就爲 alert-success,這時顯示的消息背景顏色就是綠色,傳入的是 dangerous,則顯示的就是紅色。

評論發佈成功和失敗的消息效果以下圖:

顯示評論內容

爲了避免改動已有的視圖函數的代碼,評論數據咱們也使用自定義的模板標籤來實現。模板標籤代碼以下:

@register.inclusion_tag('comments/inclusions/_list.html', takes_context=True)
def show_comments(context, post):
    comment_list = post.comment_set.all().order_by('-created_time')
    comment_count = comment_list.count()
    return {
        'comment_count': comment_count,
        'comment_list': comment_list,
    }
複製代碼

咱們使用了 post.comment_set.all() 來獲取 post 對應的所有評論。 CommentPost 是經過 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()

模板 _list.html 代碼以下:

<h3>評論列表,共 <span>{{ comment_count }}</span> 條評論</h3>
<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" datetime="{{ comment.created_time }}">{{ comment.created_time }}</time>
      <div class="text">
        {{ comment.text|linebreaks }}
      </div>
    </li>
  {% empty %}
    暫無評論
  {% endfor %}
</ul>
複製代碼

要注意這裏 {{ comment.text|linebreaks }} 中對評論內容使用的過濾器 linebreaks,瀏覽器會將換行以及連續的多個空格合併爲一個空格。若是用戶評論的內容中有換行,瀏覽器會將換行替換爲空格,從而顯示的用戶評論內容就會擠成一堆。linebreaks 過濾器預先將換行符替換爲 br HTML 標籤,這樣內容就能換行顯示了。

而後將 detail.html 中此前佔位用的評論模板替換爲模板標籤渲染的內容:

<h3>發表評論</h3>
{% show_comment_form post %}
<div class="comment-list-panel">
    {% show_comments post %}
</div>
複製代碼

訪問文章詳情頁,能夠看到已經發表的評論列表了:

大功告成!

歡迎關注 HelloGitHub 公衆號,獲取更多開源項目的資料和內容

『講解開源項目系列』 ——讓對開源項目感興趣的人再也不畏懼、讓開源項目的發起者再也不孤單。跟着咱們的文章,你會發現編程的樂趣、使用和發現參與開源項目如此簡單。歡迎留言聯繫咱們、加入咱們,讓更多人愛上開源、貢獻開源~

相關文章
相關標籤/搜索