13. 評論

本教程內(nèi)容已過時,更新版教程請訪問: Django 博客開發(fā)入門教程。

這是 Django 博客教程的第 13 篇,在閱讀此篇教程以前,請確保你已閱讀 Django 博客教程的前 12 篇:
1. Django 博客教程:前言
2. 搭建開發(fā)環(huán)境
3. 建立我們的 django 博客應用
4. 創(chuàng)建 django 博客的數(shù)據(jù)庫模型
5. 讓 django 完成翻譯——遷移數(shù)據(jù)庫模型
6. django 博客首頁視圖
7. 真正的 django 博客首頁視圖
8. 在 django admin 后臺發(fā)布我們的文章
9. 博客文章詳情頁
10. 支持 markdown 語法和代碼高亮
11. 頁面?zhèn)冗厵?/a>
12. 分類與歸檔

相對來說,評論其實是另外一個比較獨立的功能了。django 提倡,如果功能相對比較獨立的話,最好是創(chuàng)建一個 app,把相應的功能代碼寫到這個 app 里。我們的第一個 app 叫 blog,它里面放了展示博客文章列表和細節(jié)等相關功能的代碼。而這里我們再創(chuàng)建一個 app,名為 comments,這里面將存放和評論功能相關的代碼。首先激活虛擬環(huán)境,然后輸入如下命令創(chuàng)建一個新的應用:

python manage.py startapp comments

我們可以看到生成的 app 目錄結構是一樣的。關于創(chuàng)建 app 以及 django 的目錄結構前面已經(jīng)有過介紹,如果你需要復習的話請[點擊這里][]。創(chuàng)建新的 app 后一定要記得在 settings 里注冊這個 app,django 才知道這是一個 app:

blogproject/settings.py

...
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog',
    # 注冊新創(chuàng)建的 comments app
    'comments', 
]
...

設計評論的數(shù)據(jù)庫模型

用戶評論的數(shù)據(jù)必須被存儲到數(shù)據(jù)庫,以便其他用戶訪問時 django 能從數(shù)據(jù)庫取回這些數(shù)據(jù)然后展示給訪問的用戶,因此我們需要為評論設計數(shù)據(jù)庫模型,這和設計文章、分類、標簽的數(shù)據(jù)庫模型是一樣的,如果你忘了怎么做,回這里參考再復習一下我們當初的做法吧。我們的評論模型設計如下:

comments/models.py

@python_2_unicode_compatible
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(個人網(wǎng)站),用戶發(fā)表的內(nèi)容將存放在 text 字段里,created_time 記錄評論時間。最后,這個評論是關聯(lián)到某篇文章(Post)的,由于一個評論只能屬于一篇文章,一篇文章可以有多個評論,是一對多的關系,因此這里我們使用了 ForeignKey。關于 ForeKey的只是我們前面已有介紹。

創(chuàng)建了數(shù)據(jù)庫模型就要遷移,遷移數(shù)據(jù)庫的命令也在前面講過。在虛擬環(huán)境下運行以下的命令:

python manage.py makemigrations

python manage.py migrate

評論表單設計

這一節(jié)我們將學習一個全新的 django 知識:表單。那么什么是表單呢?基本的 html 知識告訴我們,在 html 文檔中這樣的代碼表示一個表單:

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

為什么需要表單呢?考慮用戶在我們博客網(wǎng)站上發(fā)表評論的過程。當用戶想要發(fā)表評論時,他找到我們給他展示的一個評論表單(我們已經(jīng)看到在文章詳情頁的底部就有一個評論表單,你將看到表單呈現(xiàn)給我們的樣子),然后根據(jù)表單的要求填寫相應的數(shù)據(jù)。之后用戶點擊評論按鈕,這些數(shù)據(jù)就會發(fā)送給某個 URL。我們知道每一個 URL 對應著一個 django 的視圖函數(shù),于是 django 調(diào)用這個視圖函數(shù),我們在視圖函數(shù)中寫上處理用戶通過表單提交上來的數(shù)據(jù)的代碼,比如驗證數(shù)據(jù)的合法性并且保存數(shù)據(jù)到數(shù)據(jù)庫中,那么用戶的評論就被 django 后臺處理了。如果通過表單提交的數(shù)據(jù)存在錯誤,那么我們把錯誤信息返回給用戶,并在前端重新渲染,并要求用戶根據(jù)錯誤信息修正表單中不符合格式的數(shù)據(jù),再重新提交。

django 的表單功能就是幫我們完成上述所說的表單處理邏輯,表單對 django 來說是一個內(nèi)容豐富的話題,很難通過教程中的這么一個例子涵蓋其全部用法。因此我們強烈建議你在完成本教程后接下來的學習中仔細閱讀 django 官方文檔關于表單的介紹,因為表單在 web 開發(fā)中會經(jīng)常遇到。

下面開始編寫評論表單代碼。在 comments 目錄下(和 models.py 同級)創(chuàng)建一個 forms.py 文件,我們的表單代碼如下:

blogproject/comments/forms.py

from django import forms

from .models import Comment


class CommentForm(forms.ModelForm):
    class Meta:
        model = Comment
        fields = ['name', 'email', 'url', 'text']

        widgets = {
            'name': forms.TextInput(attrs={
                'placeholder': "名字",
            }),
            'email': forms.TextInput(attrs={
                'placeholder': "郵箱",
            }),
            'url': forms.TextInput(attrs={
                'placeholder': "網(wǎng)址",
            }),
        }

要使用 django 的表單功能,我們首先導入 forms 模塊。django 的表單類必須繼承自 forms.Form 類或者 forms.ModelForm 類。如果表單對應有一個數(shù)據(jù)庫模型(例如這里的評論表單對應著評論模型),那么使用 ModelForm 類會簡單很多,這是 django 為我們提供的方便。之后我們在表單的內(nèi)部類 Meta 里指定一些和表單相關的東西。model = Comment 表明這個表單對應的數(shù)據(jù)庫模型是 Comment 類。fields = ['name', 'email', 'url', 'text'] 指定了表單需要顯示的字段,這里我們指定了 name、email、url、text 需要顯示。widgets 決定了各個字段在前端渲染的輸入框類型,例如 TextInput,在前端會被渲染成這樣的輸入框:<input type="text">,另外我們還給輸入框加了一些屬性,如 placeholder(如果不明白什么意思請查看 html 的表單部分的知識)。

關于表單進一步的解釋

django 為什么要給我們提供一個表單類呢?為了便于理解,我們可以把表單和前面講過的 django ORM 系統(tǒng)做對比?;叵胍幌?,我們使用數(shù)據(jù)庫保存我們創(chuàng)建的博客文章,但是我們從頭到尾沒有寫過任何和數(shù)據(jù)庫有關的代碼(要知道數(shù)據(jù)庫自身也有一門數(shù)據(jù)庫語言),這是因為 django 的 ORM 系統(tǒng)內(nèi)部幫我們做了一些事情。我們遵循 django 的規(guī)范寫的一些 Python 代碼,例如創(chuàng)建 Post、Category 類,然后通過運行數(shù)據(jù)庫遷移命令將這些代碼反應到數(shù)據(jù)庫。

django 的表單和這個思想類似,正常的前端表單代碼應該是和本文開頭所提及的那樣,但是我們目前并沒有寫這些代碼,而是寫了一個 CommentForm 這個 Python 類。通過調(diào)用這個類的一些方法和屬性,django 將自動為我們創(chuàng)建常規(guī)的表單代碼,接下來的教程我們就會看到具體是怎么做的。

視圖函數(shù)

當用戶提交表單中的數(shù)據(jù)后,django 需要調(diào)用相應的視圖函數(shù)來處理這些數(shù)據(jù),下面開始寫我們視圖函數(shù)處理邏輯:

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):
    # 先獲取被評論的文章,因為后面需要把評論和被評論的文章關聯(lián)起來
    # 這里我們使用了 django 提供的一個快捷函數(shù) get_object_or_404
    # 這個函數(shù)的作用是當獲取的文章(Post)存在時,則獲??;否則返回 404 頁面給用戶
    post = get_object_or_404(Post, pk=post_pk)
    
    # http 請求有 get 和 post 兩種方法,一般用戶通過表單提交數(shù)據(jù)都是通過 post 請求,
    # 因此只有當用戶的請求為 post 時才需要處理表單數(shù)據(jù)
    if request.method == 'POST':
        # 用戶提交的數(shù)據(jù)存在 request.POST
        # 我們利用這些數(shù)據(jù)構造了 CommentForm 的實例,這樣 django 的表單就生成了
        form = CommentForm(request.POST)
        
        # 當調(diào)用 form.is_valid() 方法時,django 自動幫我們檢查表單的數(shù)據(jù)是否符合格式要求
        if form.is_valid():
            # 檢查到數(shù)據(jù)是合法的,調(diào)用表單的 save 方法保存數(shù)據(jù)到數(shù)據(jù)庫
            # commit=False 的作用是僅僅利用表單的數(shù)據(jù)生成 Comment 模型類的實例
            # 但還不保存數(shù)據(jù)到數(shù)據(jù)庫
            comment = form.save(commit=False)
            # 將評論和被評論的文章關聯(lián)起來
            comment.post = post
            # 最終將評論數(shù)據(jù)保存進數(shù)據(jù)庫,調(diào)用模型實例的 save 方法
            comment.save()
            
            # 重定向到 post 的詳情頁
            return redirect(post)

        else:
            # 檢查到數(shù)據(jù)不合法,重新渲染詳情頁,并且渲染表單的錯誤
            # 因此我們傳了三個模板變量給 detail.html
            # 一個是文章(Post),一個是評論列表,一個是表單 form
            # 注意這里我們用到了 post.comment_set.all() 方法
            # 這個用法有點類似于 Post.objects.all()
            # 其作用是獲取這篇 post 下的的全部評論
            # 因為 Post 和 Comment 是 ForeignKey 關聯(lián)的
            # 因此使用 post.comment_set.all() 反向查詢?nèi)吭u論
            # 正向查詢就直接是 comment.post
            comment_list = post.comment_set.all()
            context = {'post': post,
                       'form': form,
                       'comment_list': comment_list
                       }
            return render(request, 'blog/detail.html', context=context)
    # 不是 post 請求,說明用戶沒有提交數(shù)據(jù),重定向到文章詳情頁
    return redirect(post)

綁定 URL

視圖函數(shù)需要和 URL 綁定,這里我們在 comment app 中再建一個 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'),
]

最后要在項目目錄的 urls.py 里關聯(lián)這個 urls.py 文件:

blogproject/urls.py

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'', include('blog.urls')),
    + url(r'', include('comments.urls')),
]

更新文章詳情頁面的視圖函數(shù)

我們可以看到評論表單和評論列表是位于文章詳情頁面的,處理文章詳情頁面的視圖函數(shù)是 detail,相應地需要更新 detail,讓它生成表單和從數(shù)據(jù)庫獲取評論數(shù)據(jù),然后傳遞給模板顯示:

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',
                                  ])
    form = CommentForm()
    # 獲取這篇 post 下的全部評論
    comment_list = post.comment_set.all()
    context = {'post': post,
               'form': form,
               'comment_list': comment_list
               }
    return render(request, 'blog/detail.html', context=context)

在前端渲染表單

使用 django 表單的一個好處就是 django 能幫我們自動渲染表單。我們在表單的視圖函數(shù)里傳遞了一個 form 變量給模板,這個變量就包含了自動生成 html 表單的全部數(shù)據(jù)。在 detail.html 中通過 form 來自動生成表單。刪掉原來用于占位的html 表單代碼,即下面這段代碼:

<form action="#" method="post" class="comment-form">
  <div class="row">
    <div class="col-md-4">
      <input type="text" name="name" placeholder="名字" 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 }}">Name:</label>
      {{ form.name }}
      {{ form.name.errors }}
    </div>
    <div class="col-md-4">
      <label for="{{ form.email.id_for_label }}">Email:</label>
      {{ form.email }}
      {{ form.name.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 }}">Comment:</label>
      {{ form.text }}
      {{ form.text.errors }}
      <button type="submit"><span>發(fā)表</span></button>
    </div>
  </div>    <!-- row -->
</form>

顯示評論內(nèi)容

在 detail 視圖函數(shù)我們獲取了全部評論數(shù)據(jù),并通過 comment_list 傳遞給了模板。和處理 index 頁面的文章列表方式是一樣的,我們在模板中通過 {% for %} 模板標簽來循環(huán)顯示文章對應的全部評論內(nèi)容。

刪掉占位用的評論內(nèi)容的 html 代碼,即如下的代碼:

<div class="comment-list">
  <h2>評論列表</h2>
  <ul class="list-unstyled">
    <li class="comment-item">
      <span class="name">追夢人物</span>
      <time class="date">2017年3月12日 14:56</time>
      <div class="text">
        還不錯哦
      </div>
    </li>
    ...
  </ul>
</div>

替換成如下的代碼:

<div class="comment-list">
  <h2>評論列表</h2>
  <ul class="list-unstyled">
    {% for comment in comment_list %}
    <li class="comment-item">
      <span class="name">{{ comment.name }}</span>
      <time class="date">{{ comment.created_time }}</time>
      <div class="text">
        {{ comment.text }}
      </div>
    </li>
    {% empty %}
    暫無評論
    {% endfor %}
  </ul>
</div>
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容