Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 日韩欧美在线中文字幕,欧美日韩网站,国产成人精视频在线观看免费

          整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          WordPress主題開發(fā)教程二十五:留言模板

          篇將涉及到博客一個(gè)比較重要的東西;評(píng)論模板。

          你應(yīng)該知道:

          • 沒(méi)有快速的方式在 comments.php 建立評(píng)論模板
          • 大部分的 WordPress 設(shè)計(jì)者使用來(lái)自 WordPress 默認(rèn)主題(Kubrick)默認(rèn)評(píng)論模板根據(jù)。
          • 一些設(shè)計(jì)者會(huì)修改默認(rèn)的評(píng)論模板去適合他們自己的需求。
          • 你將使用我的默認(rèn)評(píng)論模板的修改版本。

          第1步:創(chuàng)建 comments.php

          • 創(chuàng)建一個(gè)新文件:comments.php
          • 把我的 comments.txt 將文件中的內(nèi)容復(fù)制到 comments.php。
          • 保存 comments.php 文件。

          第2步:樣式化留言

          • 把我的 comments-template-css 文件中的內(nèi)容拷貝到你的 style.css 文件中。
          • 復(fù)制到 style.css 的底部或者剛好 #footer 的上面。

          第3步:在 single.php 添加留言模板

          single.php 文件中,entry DIV 的下面,輸入以下代碼:

          <div class=”comments-template”>
          <?php comments_template(); ?>
          </div>

          comments_template() 這個(gè)函數(shù)是用來(lái)重復(fù) comments.php 文件調(diào)用評(píng)論模板。comments.php 文件然后就會(huì)根據(jù)它的模板(或者代碼)去顯示評(píng)論列表。列表中的每個(gè)條目是一條評(píng)論。

          如果想讓人們可以在靜態(tài)頁(yè)面也可以留言,同樣可以把 comments_template() 函數(shù)用到 page.php 文件。

          第4步:驗(yàn)證代碼

          第四步是驗(yàn)證你的代碼,然而可以不進(jìn)行第四步的,因?yàn)槟阍谑褂玫氖俏乙呀?jīng)整理過(guò)的默認(rèn)主題評(píng)論模板的修改版。我已經(jīng)替你驗(yàn)證過(guò)代碼了。

          驗(yàn)證:

          • 查看 > 頁(yè)面源代碼
          • 拷貝所有源代碼
          • 然后到 validator。
          • 把你的代碼粘貼到 Direct Input 框中。
          • 點(diǎn)擊 Check

          以后的參考(當(dāng)你創(chuàng)建你自己的主題和評(píng)論模板),下面是需要驗(yàn)證的頁(yè)面:

          • 主頁(yè) — Home page
          • 存檔頁(yè)面 — Archive pages
          • 類別頁(yè)面 — Category pages (如果你自定義了類別頁(yè)面)
          • 搜索結(jié)果頁(yè)面 — Search result pages
          • 靜態(tài)頁(yè)面 — Pages (如:About)
          • 單一日志頁(yè)面 — Single post view page
          • 單一日志沒(méi)有留言 — Single post with no comments
          • 單一日志有留言 — Single post with comments
          • 單一日志含有必須登錄信息 — Single post with must login message
          • 單一日志沒(méi)有必須登錄信息 — Single post with no login required message
          • 密碼保護(hù)的單一日志并有留言 — Password protected single post with comments

          評(píng)論模板的進(jìn)一步解釋

          • 評(píng)論模板從根本上說(shuō)是一個(gè)有序列表(OL),不是無(wú)序的,盡管它們基本上同樣方式工作。 無(wú)序列表是以圓點(diǎn)列表組織的。有序列表則是以數(shù)字列表組織的(每個(gè)條目都有一個(gè)數(shù)字,從1開始)。
          • single.php 文件中,你用 comments-template DIV 圍住comments_template()?,F(xiàn)在你的評(píng)論模板在一個(gè) DIV 標(biāo)簽中的一個(gè)有序列表中。

          當(dāng)你你的日志是密碼保護(hù)的,你的評(píng)論同樣是密碼保護(hù)的:

          這個(gè)修改版的留言模板有一個(gè) H2 子標(biāo)題顯示 Password Protected。默認(rèn)的留言模板是沒(méi)有的。

          下面展示了哪些東西組成了你的留言列表:

          簡(jiǎn)單整理下就是:

          comment_text() 函數(shù)就是用來(lái)調(diào)用每條留言的。

          我不會(huì)解釋留言模板的原因 CSS 代碼的意思。不像 comments.php 文件中的代碼,你可以隨便測(cè)試你的 CSS 代碼而不會(huì)弄壞留言模板。自己去測(cè)試會(huì)比我的解釋對(duì)你更有好處。

          者:HelloGitHub-追夢(mèng)人物

          截止到目前為止我們的 django blog 文章展示部分,已經(jīng)實(shí)現(xiàn)的“八九不離十”了。你以為本系列文章就要結(jié)束了嗎?不能夠!新的征程才剛剛開始,HelloDjango 系列文章剛剛過(guò)半,后面的文章你將接觸更多博客系統(tǒng)的細(xì)節(jié)。向著一個(gè)小而全的博客系統(tǒng)前進(jìn)、前進(jìn)、前進(jìn),你定會(huì)收獲頗多。

          今天我們就來(lái)開啟博客的評(píng)論功能,建起和讀者的溝通橋梁。

          創(chuàng)建評(píng)論應(yīng)用

          相對(duì)來(lái)說(shuō),評(píng)論是另外一個(gè)比較獨(dú)立的功能。Django 提倡,如果功能相對(duì)比較獨(dú)立的話,最好是創(chuàng)建一個(gè)應(yīng)用,把相應(yīng)的功能代碼組織到這個(gè)應(yīng)用里。我們的第一個(gè)應(yīng)用叫 blog,它里面放了展示博客文章列表和詳情等相關(guān)功能的代碼。而這里我們?cè)賱?chuàng)建一個(gè)應(yīng)用,名為 comments 這里面將存放和評(píng)論功能相關(guān)的代碼。首先進(jìn)入到項(xiàng)目根目錄,然后輸入如下命令創(chuàng)建一個(gè)新的應(yīng)用:

          > pipenv run python manage.py startapp comments
          

          可以看到生成的 comments 應(yīng)用目錄結(jié)構(gòu)和 blog 應(yīng)用的目錄是類似的(關(guān)于創(chuàng)建應(yīng)用以及應(yīng)用的目錄結(jié)構(gòu)在 ["空空如也"的博客應(yīng)用](https://www.zmrenwu.com/courses/hellodjango-blog-tutorial/materials/60/ ""空空如也"的博客應(yīng)用") 中已經(jīng)有過(guò)詳細(xì)介紹)。

          創(chuàng)建新的應(yīng)用后一定要記得在 settings.py 里注冊(cè)這個(gè)應(yīng)用,django 才知道這是一個(gè)應(yīng)用。

          blogproject/settings.py
          ?
          ...
          INSTALLED_APPS = [
           ...
           'blog.apps.BlogConfig', # 注冊(cè) blog 應(yīng)用
           'comments.apps.CommentsConfig', # 注冊(cè) comments 應(yīng)用
          ]v
          ...
          

          注意這里注冊(cè)的是 CommentsConfig 類,在 博客從“裸奔”到“有皮膚”[1] 中曾經(jīng)講過(guò)如何對(duì)應(yīng)用做一些初始化配置,例如讓 blog 應(yīng)用在 django 的 admin 后臺(tái)顯示中文名字。這里也對(duì)評(píng)論應(yīng)用做類似的配置:

          comments/app.py
          ?
          from django.apps import AppConfig
          ?
          ?
          class CommentsConfig(AppConfig):
           name = 'comments'
           verbose_name = '評(píng)論'
          

          設(shè)計(jì)評(píng)論的數(shù)據(jù)庫(kù)模型

          用戶評(píng)論的數(shù)據(jù)必須被存儲(chǔ)到數(shù)據(jù)庫(kù)里,以便其他用戶訪問(wèn)時(shí) django 能從數(shù)據(jù)庫(kù)取回這些數(shù)據(jù)然后展示給訪問(wèn)的用戶,因此我們需要為評(píng)論設(shè)計(jì)數(shù)據(jù)庫(kù)模型,這和設(shè)計(jì)文章、分類、標(biāo)簽的數(shù)據(jù)庫(kù)模型是一樣的,如果你忘了怎么做,再回顧一下 創(chuàng)建 Django 博客的數(shù)據(jù)庫(kù)模型[2] 中的做法。我們的評(píng)論模型設(shè)計(jì)如下(評(píng)論模型的代碼寫在 comments\models.py 里):

          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('網(wǎng)址', blank=True)
           text = models.TextField('內(nèi)容')
           created_time = models.DateTimeField('創(chuàng)建時(shí)間', default=timezone.now)
           post = models.ForeignKey('blog.Post', verbose_name='文章', on_delete=models.CASCADE)
          ?
           class Meta:
           verbose_name = '評(píng)論'
           verbose_name_plural = verbose_name
          ?
           def __str__(self):
           return '{}: {}'.format(self.name, self.text[:20])
          

          評(píng)論會(huì)保存評(píng)論用戶的 name(名字)、email(郵箱)、url(個(gè)人網(wǎng)站,可以為空),用戶發(fā)表的內(nèi)容將存放在 text 字段里,created_time 記錄評(píng)論時(shí)間。最后,這個(gè)評(píng)論是關(guān)聯(lián)到某篇文章(Post)的,由于一個(gè)評(píng)論只能屬于一篇文章,一篇文章可以有多個(gè)評(píng)論,是一對(duì)多的關(guān)系,因此這里我們使用了 ForeignKey。關(guān)于 ForeignKey 我們前面已有介紹,這里不再贅述。

          此外,在 博客從“裸奔”到“有皮膚”[3] 中提過(guò),所有模型的字段都接受一個(gè) verbose_name 參數(shù)(大部分是第一個(gè)位置參數(shù)),django 在根據(jù)模型的定義自動(dòng)生成表單時(shí),會(huì)使用這個(gè)參數(shù)的值作為表單字段的 label,我們?cè)诤竺娑x的評(píng)論表單時(shí)會(huì)進(jìn)一步看到其作用。

          創(chuàng)建了數(shù)據(jù)庫(kù)模型就要遷移數(shù)據(jù)庫(kù),遷移數(shù)據(jù)庫(kù)的命令也在前面講過(guò)。在項(xiàng)目根目錄下分別運(yùn)行下面兩條命令:

          > pipenv run python manage.py makemigrations
          > pipenv run python manage.py migrate
          

          注冊(cè)評(píng)論模型到 admin

          既然已經(jīng)創(chuàng)建了模型,我們就可以將它注冊(cè)到 django admin 后臺(tái),方便管理員用戶對(duì)評(píng)論進(jìn)行管理,如何注冊(cè) admin 以及美化在 博客從“裸奔”到“有皮膚”[4] 有過(guò)詳細(xì)介紹,這里給出相關(guān)代碼:

          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)
          

          設(shè)計(jì)評(píng)論表單

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

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

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

          django 的表單功能就是幫我們完成上述所說(shuō)的表單處理邏輯,表單對(duì) django 來(lái)說(shuō)是一個(gè)內(nèi)容豐富的話題,很難通過(guò)教程中的這么一個(gè)例子涵蓋其全部用法。因此我們強(qiáng)烈建議你在完成本教程后接下來(lái)的學(xué)習(xí)中仔細(xì)閱讀 django 官方文檔關(guān)于 表單[5] 的介紹,因?yàn)楸韱卧?Web 開發(fā)中會(huì)經(jīng)常遇到。

          下面開始編寫評(píng)論表單代碼。在 comments\ 目錄下(和 models.py 同級(jí))新建一個(gè) forms.py 文件,用來(lái)存放表單代碼,我們的表單代碼如下:

          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 的表單功能,我們首先導(dǎo)入 forms 模塊。django 的表單類必須繼承自 forms.Form 類或者 forms.ModelForm 類。如果表單對(duì)應(yīng)有一個(gè)數(shù)據(jù)庫(kù)模型(例如這里的評(píng)論表單對(duì)應(yīng)著評(píng)論模型),那么使用 ModelForm 類會(huì)簡(jiǎn)單很多,這是 django 為我們提供的方便。之后我們?cè)诒韱蔚膬?nèi)部類 Meta 里指定一些和表單相關(guān)的東西。model = Comment 表明這個(gè)表單對(duì)應(yīng)的數(shù)據(jù)庫(kù)模型是 Comment 類。fields = ['name', 'email', 'url', 'text'] 指定了表單需要顯示的字段,這里我們指定了 name、email、url、text 需要顯示。

          關(guān)于表單進(jìn)一步的解釋

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

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

          展示評(píng)論表單

          表單類已經(jīng)定義完畢,現(xiàn)在的任務(wù)是在文章的詳情頁(yè)下方將這個(gè)表單展現(xiàn)給用戶,用戶便可以通過(guò)這個(gè)表單填寫評(píng)論數(shù)據(jù),從而發(fā)表評(píng)論。

          那么怎么展現(xiàn)一個(gè)表單呢?django 會(huì)根據(jù)表單類的定義自動(dòng)生成表單的 HTML 代碼,我們要做的就是實(shí)例化這個(gè)表單類,然后將表單的實(shí)例傳給模板,讓 django 的模板引擎來(lái)渲染這個(gè)表單。

          那怎么將表單類的實(shí)例傳給模板呢?因?yàn)楸韱纬霈F(xiàn)在文章詳情頁(yè),一種想法是修改文章詳情頁(yè) detail 視圖函數(shù),在這個(gè)視圖中實(shí)例化一個(gè)表單,然后傳遞給模板。然而這樣做的一個(gè)缺點(diǎn)就是需要修改 detail 視圖函數(shù)的代碼,而且 detail 視圖函數(shù)的作用主要就是處理文章詳情,一個(gè)視圖函數(shù)最好不要讓它做太多雜七雜八的事情。另外一種想法是使用自定義的模板標(biāo)簽,我們?cè)?頁(yè)面?zhèn)冗厵冢菏褂米远x模板標(biāo)簽[6] 中詳細(xì)介紹過(guò)如何自定義模板標(biāo)簽來(lái)渲染一個(gè)局部的 HTML 頁(yè)面,這里我們使用自定義模板標(biāo)簽的方法,來(lái)渲染表單頁(yè)面。

          和 blog 應(yīng)用中定義模板標(biāo)簽的老套路一樣,首先建立評(píng)論應(yīng)用模板標(biāo)簽的文件結(jié)構(gòu),在 comments\ 文件夾下新建一個(gè) templatetags 文件夾,然后創(chuàng)建 __init__.py 文件使其成為一個(gè)包,再創(chuàng)建一個(gè) comments_extras.py 文件用于存放模板標(biāo)簽的代碼,文件結(jié)構(gòu)如下:

          ...
          blog\
          comments\
           templatetags\
           __init__.py
           comments_extras.py
          ...
          

          然后我們定義一個(gè) inclusion_tag 類型的模板標(biāo)簽,用于渲染評(píng)論表單,關(guān)于如何定義模板標(biāo)簽,在 頁(yè)面?zhèn)冗厵冢菏褂米远x模板標(biāo)簽[7] 中已經(jīng)有詳細(xì)介紹,這里不再贅述。

          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 模板標(biāo)簽使用時(shí)會(huì)接受一個(gè) post(文章 Post 模型的實(shí)例)作為參數(shù),同時(shí)也可能傳入一個(gè)評(píng)論表單 CommentForm 的實(shí)例 form,如果沒(méi)有接受到評(píng)論表單參數(shù),模板標(biāo)簽就會(huì)新創(chuàng)建一個(gè) CommentForm 的實(shí)例(一個(gè)沒(méi)有綁定任何數(shù)據(jù)的空表單)傳給模板,否則就直接將接受到的評(píng)論表單實(shí)例直接傳給模板,這主要是為了復(fù)用已有的評(píng)論表單實(shí)例(后面會(huì)看到其用法)。

          然后在 templates/comments/inclusions 目錄下(沒(méi)有就新建)新建一個(gè) _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">發(fā)表</button>
           </div>
           </div> <!-- row -->
          </form>
          

          這個(gè)表單的模板有點(diǎn)復(fù)雜,一一講解一下。

          首先 HTML 的 form 標(biāo)簽有 2 個(gè)重要的屬性,action 和 method。action 指定表單內(nèi)容提交的地址,這里我們提交給 comments:comment 視圖函數(shù)對(duì)應(yīng)的 URL(后面會(huì)創(chuàng)建這個(gè)視圖函數(shù)并綁定對(duì)應(yīng)的 URL),模板標(biāo)簽 url 的用法在 分類、歸檔和標(biāo)簽頁(yè)[8] 教程中有詳細(xì)介紹。method 指定提交表單時(shí)的 HTTP 請(qǐng)求類型,一般表單提交都是使用 POST。

          然后我們看到 {% csrf_token %},這個(gè)模板標(biāo)簽在表單渲染時(shí)會(huì)自動(dòng)渲染為一個(gè)隱藏類型的 HTML input 控件,其值為一個(gè)隨機(jī)字符串,作用主要是為了防護(hù) CSRF(跨站請(qǐng)求偽造)攻擊。{% csrf_token %} 在模板中渲染出來(lái)的內(nèi)容大概如下所示:

          <input type="hidden" name="csrfmiddlewaretoken" value="KH9QLnpQPv2IBcv3oLsksJXdcGvKSnC8t0mTfRSeNIlk5T1G1MBEIwVhK4eh6gIZ">
          

          CSRF 攻擊是一種常見的 Web 攻擊手段。攻擊者利用用戶存儲(chǔ)在瀏覽器中的 cookie,向目標(biāo)網(wǎng)站發(fā)送 HTTP 請(qǐng)求,這樣在目標(biāo)網(wǎng)站看來(lái),請(qǐng)求來(lái)自于用戶,而實(shí)際發(fā)送請(qǐng)求的人卻是攻擊者。例如假設(shè)我們的博客支持登錄功能(目前沒(méi)有),并使用 cookie(或者 session)記錄用戶的登錄狀態(tài),且評(píng)論表單沒(méi)有 csrf token 防護(hù)。用戶登錄了我們的博客后,又去訪問(wèn)了一個(gè)小電影網(wǎng)站,小電影網(wǎng)站有一段惡意 JavaScript 腳本,它讀取用戶的 cookie,并構(gòu)造了評(píng)論表單的數(shù)據(jù),然后腳本使用這個(gè) cookie 向我們的博客網(wǎng)站發(fā)送一條 POST 請(qǐng)求,django 就會(huì)認(rèn)為這是來(lái)自該用戶的評(píng)論發(fā)布請(qǐng)求,便會(huì)在后臺(tái)創(chuàng)建一個(gè)該用戶的評(píng)論,而這個(gè)用戶全程一臉懵逼。

          CSRF 的一個(gè)防范措施是,對(duì)所有訪問(wèn)網(wǎng)站的用戶頒發(fā)一個(gè)令牌(token),對(duì)于敏感的 HTTP 請(qǐng)求,后臺(tái)會(huì)校驗(yàn)此令牌,確保令牌的確是網(wǎng)站頒發(fā)給指定用戶的。因此,當(dāng)用戶訪問(wèn)別的網(wǎng)站時(shí),雖然攻擊者可以拿到用戶的 cookie,但是無(wú)法取得證明身份的令牌,因此發(fā)過(guò)來(lái)的請(qǐng)求便不會(huì)被受理。

          以上是對(duì) CSRF 攻擊和防護(hù)措施的一個(gè)簡(jiǎn)單介紹,更加詳細(xì)的講解請(qǐng)使用搜索引擎搜索相關(guān)資料。

          show_comment_form 模板標(biāo)簽給模板傳遞了一個(gè)模板變量 form,它是 CommentForm 的一個(gè)實(shí)例,表單的字段 {{ form.name }}、{{ form.email }}、{{ form.url }} 等將自動(dòng)渲染成表單控件,例如 <input> 控件。

          注意到表單的定義中并沒(méi)有定義 name、email、url 等屬性,那它們是哪里來(lái)的呢?看到 CommentForm 中 Meta 下的 fields,django 會(huì)自動(dòng)將 fields 中聲明的模型字段設(shè)置為表單的屬性。

          {{ form.name.errors }}、{{ form.email.errors }} 等將渲染表單對(duì)應(yīng)字段的錯(cuò)誤(如果有的話),例如用戶 email 格式填錯(cuò)了,那么 django 會(huì)檢查用戶提交的 email 的格式,然后將格式錯(cuò)誤信息保存到 errors 中,模板便將錯(cuò)誤信息渲染顯示。

          {{ form.xxx.label }} 用來(lái)獲取表單的 label,之前說(shuō)過(guò),django 根據(jù)表單對(duì)應(yīng)的模型中字段的 verbose_name 參數(shù)生成。

          然后我們就可以在 detail.html 中使用這個(gè)模板標(biāo)簽來(lái)渲染表單了,注意在使用前記得先 {% load comment_extras %} 這個(gè)模塊。而且為了避免可能的報(bào)錯(cuò),最好重啟一下開發(fā)服務(wù)器。

          {% extends 'base.html' %}
          {% load comment_extras %}
          ...
          ?
          <h3>發(fā)表評(píng)論</h3>
          {% show_comment_form post %}
          

          這里當(dāng)用戶訪問(wèn)文章詳情頁(yè)面時(shí),我們給他展示一個(gè)空表單,所以這里只傳入了 post 參數(shù)需要的值,而沒(méi)有傳入 form 參數(shù)所需的值。可以看到表單渲染出來(lái)的結(jié)果了:

          評(píng)論視圖函數(shù)

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

          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):
           # 先獲取被評(píng)論的文章,因?yàn)楹竺嫘枰言u(píng)論和被評(píng)論的文章關(guān)聯(lián)起來(lái)。
           # 這里我們使用了 django 提供的一個(gè)快捷函數(shù) get_object_or_404,
           # 這個(gè)函數(shù)的作用是當(dāng)獲取的文章(Post)存在時(shí),則獲取;否則返回 404 頁(yè)面給用戶。
           post = get_object_or_404(Post, pk=post_pk)
          ?
           # django 將用戶提交的數(shù)據(jù)封裝在 request.POST 中,這是一個(gè)類字典對(duì)象。
           # 我們利用這些數(shù)據(jù)構(gòu)造了 CommentForm 的實(shí)例,這樣就生成了一個(gè)綁定了用戶提交數(shù)據(jù)的表單。
           form = CommentForm(request.POST)
          ?
           # 當(dāng)調(diào)用 form.is_valid() 方法時(shí),django 自動(dòng)幫我們檢查表單的數(shù)據(jù)是否符合格式要求。
           if form.is_valid():
           # 檢查到數(shù)據(jù)是合法的,調(diào)用表單的 save 方法保存數(shù)據(jù)到數(shù)據(jù)庫(kù),
           # commit=False 的作用是僅僅利用表單的數(shù)據(jù)生成 Comment 模型類的實(shí)例,但還不保存評(píng)論數(shù)據(jù)到數(shù)據(jù)庫(kù)。
           comment = form.save(commit=False)
          ?
           # 將評(píng)論和被評(píng)論的文章關(guān)聯(lián)起來(lái)。
           comment.post = post
          ?
           # 最終將評(píng)論數(shù)據(jù)保存進(jìn)數(shù)據(jù)庫(kù),調(diào)用模型實(shí)例的 save 方法
           comment.save()
          ?
           # 重定向到 post 的詳情頁(yè),實(shí)際上當(dāng) redirect 函數(shù)接收一個(gè)模型的實(shí)例時(shí),它會(huì)調(diào)用這個(gè)模型實(shí)例的 get_absolute_url 方法,
           # 然后重定向到 get_absolute_url 方法返回的 URL。
           return redirect(post)
          ?
           # 檢查到數(shù)據(jù)不合法,我們渲染一個(gè)預(yù)覽頁(yè)面,用于展示表單的錯(cuò)誤。
           # 注意這里被評(píng)論的文章 post 也傳給了模板,因?yàn)槲覀冃枰鶕?jù) post 來(lái)生成表單的提交地址。
           context = {
           'post': post,
           'form': form,
           }
           return render(request, 'comments/preview.html', context=context)
          

          這個(gè)評(píng)論視圖相比之前的一些視圖復(fù)雜了很多,主要是處理評(píng)論的過(guò)程更加復(fù)雜。具體過(guò)程在代碼中已有詳細(xì)注釋,這里僅就視圖中出現(xiàn)了一些新的知識(shí)點(diǎn)進(jìn)行講解。

          首先視圖函數(shù)被 require_POST 裝飾器裝飾,從裝飾器的名字就可以看出,其作用是限制這個(gè)視圖只能通過(guò) POST 請(qǐng)求觸發(fā),因?yàn)閯?chuàng)建評(píng)論需要用戶通過(guò)表單提交的數(shù)據(jù),而提交表單通常都是限定為 POST 請(qǐng)求,這樣更加安全。

          另外我們使用了 redirect 快捷函數(shù)。這個(gè)函數(shù)位于 django.shortcuts 模塊中,它的作用是對(duì) HTTP 請(qǐng)求進(jìn)行重定向(即用戶訪問(wèn)的是某個(gè) URL,但由于某些原因,服務(wù)器會(huì)將用戶重定向到另外的 URL)。redirect 既可以接收一個(gè) URL 作為參數(shù),也可以接收一個(gè)模型的實(shí)例作為參數(shù)(例如這里的 post)。如果接收一個(gè)模型的實(shí)例,那么這個(gè)實(shí)例必須實(shí)現(xiàn)了 get_absolute_url 方法,這樣 redirect 會(huì)根據(jù) get_absolute_url 方法返回的 URL 值進(jìn)行重定向。

          如果用戶提交的數(shù)據(jù)合法,我們就將評(píng)論數(shù)據(jù)保存到數(shù)據(jù)庫(kù),否則說(shuō)明用戶提交的表單包含錯(cuò)誤,我們將渲染一個(gè) preview.html 頁(yè)面,來(lái)展示表單中的錯(cuò)誤,以便用戶修改后重新提交。preview.html 的代碼如下:

          {% extends 'base.html' %}
          {% load comment_extras %}
          ?
          {% block main %}
           {% show_comment_form post form %}
          {% endblock main %}
          

          這里還是使用 show_comment_form 模板標(biāo)簽來(lái)展示一個(gè)表單,然而不同的是,這里我們傳入由視圖函數(shù) comment 傳來(lái)的綁定了用戶提交的數(shù)據(jù)的表單實(shí)例 form,而不是渲染一個(gè)空表單。因?yàn)橐晥D函數(shù) comment 中的表單實(shí)例是綁定了用戶提交的評(píng)論數(shù)據(jù),以及對(duì)數(shù)據(jù)進(jìn)行過(guò)合法性校驗(yàn)的表單,因此當(dāng) django 渲染這個(gè)表單時(shí),會(huì)連帶渲染用戶已經(jīng)填寫的表單數(shù)據(jù)以及數(shù)據(jù)不合法的錯(cuò)誤提示信息,而不是一個(gè)空的表單了。例如下圖,我們提交的數(shù)據(jù)中 email 格式不合法,表單校驗(yàn)了數(shù)據(jù)格式,然后渲染錯(cuò)誤提示:

          綁定 URL

          視圖函數(shù)需要和 URL 綁定,這里我們?cè)?comment 應(yīng)用中再建一個(gè) urls.py 文件,寫上 URL 模式:

          from django.urls import path
          ?
          from . import views
          ?
          app_name = 'comments'
          urlpatterns = [
           path('comment/<int:post_pk>', views.comment, name='comment'),
          ]
          

          別忘了給這個(gè)評(píng)論的 URL 模式規(guī)定命名空間,即 app_name = 'comments'。

          最后要在項(xiàng)目的 blogproject\ 目錄的 urls.py 里包含 comments\urls.py 這個(gè)文件:

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

          可以測(cè)試一下提交評(píng)論的功能了,首先嘗試輸入非法格式的數(shù)據(jù),例如將郵箱輸入為 xxx@xxx,那么評(píng)論視圖在校驗(yàn)表單數(shù)據(jù)合法性時(shí),發(fā)現(xiàn)郵箱格式不符,就會(huì)渲染 preview 頁(yè)面,展示表單中的錯(cuò)誤,將郵箱修改為正確的格式后,再次點(diǎn)擊發(fā)表,頁(yè)面就跳轉(zhuǎn)到了被評(píng)論文章的詳情頁(yè),說(shuō)明視圖正確執(zhí)行了保存表單數(shù)據(jù)到數(shù)據(jù)庫(kù)的邏輯。

          不過(guò)這里有一點(diǎn)不好的地方就是,評(píng)論成功后頁(yè)面直接跳轉(zhuǎn)到了被評(píng)論文章的詳情頁(yè),沒(méi)有任何提示,用戶也不知道評(píng)論究竟有沒(méi)有真的成功。這里我們使用 django 自帶的 messages 應(yīng)用來(lái)給用戶發(fā)送評(píng)論成功或者失敗的消息。

          發(fā)送評(píng)論消息

          django 默認(rèn)已經(jīng)為我們做好了 messages 的相關(guān)配置,直接用即可。

          兩個(gè)地方需要發(fā)送消息,第一個(gè)是當(dāng)評(píng)論成功,即評(píng)論數(shù)據(jù)成功保存到數(shù)據(jù)庫(kù)后,因此在 comment 視圖中加一句。

          from django.contrib import messages
          ?
          if form.is_valid():
           ...
           # 最終將評(píng)論數(shù)據(jù)保存進(jìn)數(shù)據(jù)庫(kù),調(diào)用模型實(shí)例的 save 方法
           comment.save()
          ?
           messages.add_message(request, messages.SUCCESS, '評(píng)論發(fā)表成功!', extra_tags='success')
           return redirect(post)
          

          這里導(dǎo)入 django 的 messages 模塊,使用 add_message 方法增加了一條消息,消息的第一個(gè)參數(shù)是當(dāng)前請(qǐng)求,因?yàn)楫?dāng)前請(qǐng)求攜帶用戶的 cookie,django 默認(rèn)將詳細(xì)存儲(chǔ)在用戶的 cookie 中。第二個(gè)參數(shù)是消息級(jí)別,評(píng)論發(fā)表成功的消息設(shè)置為 messages.SUCCESS,這是 django 已經(jīng)默認(rèn)定義好的一個(gè)整數(shù),消息級(jí)別也可以自己定義。緊接著傳入消息的內(nèi)容,最后 extra_tags 給這條消息打上額外的標(biāo)簽,標(biāo)簽值可以在展示消息時(shí)使用,比如這里我們會(huì)把這個(gè)值用在模板中的 HTML 標(biāo)簽的 class 屬性,增加樣式。

          同樣的,如果評(píng)論失敗了,也發(fā)送一條消息:

          # 檢查到數(shù)據(jù)不合法,我們渲染一個(gè)預(yù)覽頁(yè)面,用于展示表單的錯(cuò)誤。
          # 注意這里被評(píng)論的文章 post 也傳給了模板,因?yàn)槲覀冃枰鶕?jù) post 來(lái)生成表單的提交地址。
          context = {
           'post': post,
           'form': form,
          }
          messages.add_message(request, messages.ERROR, '評(píng)論發(fā)表失敗!請(qǐng)修改表單中的錯(cuò)誤后重新提交。', extra_tags='danger')
          

          發(fā)送的消息被緩存在 cookie 中,然后我們?cè)谀0逯蝎@取顯示即可。顯示消息比較好的地方是在導(dǎo)航條的下面,我們?cè)谀0?base.html 的導(dǎo)航條代碼下增加如下代碼:

          <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">×</span></button>
           {{ message }}
           </div>
           {% endfor %}
          {% endif %}
          

          這里 django 會(huì)通過(guò)全局上下文自動(dòng)把 messages 變量傳給模板,這個(gè)變量里存儲(chǔ)我們發(fā)送的消息內(nèi)容,然后就是循環(huán)顯示消息了。這里我們使用了 bootstrap 的一個(gè) alert 組件,為其設(shè)置不同的 class 會(huì)顯示不同的顏色,所以之前添加消息時(shí)傳入的 extra_tags 就派上了用場(chǎng)。比如這里 alert-{{ message.tags }},當(dāng)傳入的是 success 時(shí),類名就為 alert-success,這時(shí)顯示的消息背景顏色就是綠色,傳入的是 dangerous,則顯示的就是紅色。

          評(píng)論發(fā)布成功和失敗的消息效果如下圖:

          顯示評(píng)論內(nèi)容

          為了不改動(dòng)已有的視圖函數(shù)的代碼,評(píng)論數(shù)據(jù)我們也使用自定義的模板標(biāo)簽來(lái)實(shí)現(xiàn)。模板標(biāo)簽代碼如下:

          @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() 來(lái)獲取 post 對(duì)應(yīng)的全部評(píng)論。Comment 和Post 是通過(guò) ForeignKey 關(guān)聯(lián)的,回顧一下我們當(dāng)初獲取某個(gè)分類 cate 下的全部文章時(shí)的代碼:Post.objects.filter(category=cate)。這里 post.comment_set.all() 也等價(jià)于 Comment.objects.filter(post=post),即根據(jù) post 來(lái)過(guò)濾該 post 下的全部評(píng)論。但既然我們已經(jīng)有了一個(gè) Post 模型的實(shí)例 post(它對(duì)應(yīng)的是 Post 在數(shù)據(jù)庫(kù)中的一條記錄),那么獲取和 post 關(guān)聯(lián)的評(píng)論列表有一個(gè)簡(jiǎn)單方法,即調(diào)用它的 xxx_set 屬性來(lái)獲取一個(gè)類似于 objects 的模型管理器,然后調(diào)用其 all 方法來(lái)返回這個(gè) post 關(guān)聯(lián)的全部評(píng)論。其中 xxx_set 中的 xxx 為關(guān)聯(lián)模型的類名(小寫)。例如 Post.objects.filter(category=cate) 也可以等價(jià)寫為 cate.post_set.all()。

          模板 _list.html 代碼如下:

          <h3>評(píng)論列表,共 <span>{{ comment_count }}</span> 條評(píng)論</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 %}
           暫無(wú)評(píng)論
           {% endfor %}
          </ul>
          

          要注意這里 {{ comment.text|linebreaks }} 中對(duì)評(píng)論內(nèi)容使用的過(guò)濾器 linebreaks,瀏覽器會(huì)將換行以及連續(xù)的多個(gè)空格合并為一個(gè)空格。如果用戶評(píng)論的內(nèi)容中有換行,瀏覽器會(huì)將換行替換為空格,從而顯示的用戶評(píng)論內(nèi)容就會(huì)擠成一堆。linebreaks 過(guò)濾器預(yù)先將換行符替換為 br HTML 標(biāo)簽,這樣內(nèi)容就能換行顯示了。

          然后將 detail.html 中此前占位用的評(píng)論模板替換為模板標(biāo)簽渲染的內(nèi)容:

          <h3>發(fā)表評(píng)論</h3>
          {% show_comment_form post %}
          <div class="comment-list-panel">
           {% show_comments post %}
          </div>
          

          訪問(wèn)文章詳情頁(yè),可以看到已經(jīng)發(fā)表的評(píng)論列表了:

          大功告成!

          References

          [1] 博客從“裸奔”到“有皮膚”: https://www.zmrenwu.com/courses/hellodjango-blog-tutorial/materials/64/

          [2] 創(chuàng)建 Django 博客的數(shù)據(jù)庫(kù)模型: https://www.zmrenwu.com/courses/hellodjango-blog-tutorial/materials/61/

          [3] 博客從“裸奔”到“有皮膚”: https://www.zmrenwu.com/courses/hellodjango-blog-tutorial/materials/64/

          [4] 博客從“裸奔”到“有皮膚”: https://www.zmrenwu.com/courses/hellodjango-blog-tutorial/materials/64/

          [5] 表單: https://docs.djangoproject.com/en/2.2/topics/forms/

          [6] 頁(yè)面?zhèn)冗厵冢菏褂米远x模板標(biāo)簽: https://www.zmrenwu.com/courses/hellodjango-blog-tutorial/materials/70/

          [7]頁(yè)面?zhèn)冗厵冢菏褂米远x模板標(biāo)簽: https://www.zmrenwu.com/courses/hellodjango-blog-tutorial/materials/70/

          [8] 分類、歸檔和標(biāo)簽頁(yè): https://www.zmrenwu.com/courses/hellodjango-blog-tutorial/materials/71/

          『講解開源項(xiàng)目系列』——讓對(duì)開源項(xiàng)目感興趣的人不再畏懼、讓開源項(xiàng)目的發(fā)起者不再孤單。跟著我們的文章,你會(huì)發(fā)現(xiàn)編程的樂(lè)趣、使用和發(fā)現(xiàn)參與開源項(xiàng)目如此簡(jiǎn)單。歡迎加入我們,讓更多人愛上開源、貢獻(xiàn)開源~

          期折騰WP評(píng)論通知郵件,找到了一段挺不錯(cuò)的代碼,包含了美化樣式,沒(méi)有防范意識(shí)的把這段代碼加到了 functions.php 里面了,更何況作為代碼小白的我根本看不出來(lái)里面有什么“奇怪”的內(nèi)容……結(jié)果就是被掛馬了,各種煩心。

          實(shí)在是沒(méi)有辦法,只好選擇插件實(shí)現(xiàn)評(píng)論通知郵件。

          我選的是插件——Wenprise Better Emails

          這個(gè)插件雖然最后更新時(shí)間是一年前,但是勝在方便,插件目前不需要任何設(shè)置,安裝啟用即可。

          直接在WP后臺(tái)->插件->安裝插件,搜索框輸入Wenprise Better Emails,安裝。

          Wenprise Better Emails 插件使用 HTML 郵件的方式美化了 WordPress 的評(píng)論通知郵件。啟用該插件后,郵箱里面收到的評(píng)論通知郵件界面如下。如果不符合你的審美,可以直接修改插件目錄中 “templates” 目錄中的模版文件和 CSS 樣式文件來(lái)定制。

          下圖中的藍(lán)色值是 #348eda,查找到這個(gè)色值,替換成你想要色值就ok了。

          評(píng)論需要批準(zhǔn)時(shí),通知管理員的郵件。

          有人評(píng)論時(shí),文章作者收到的郵件。

          評(píng)論有回復(fù)時(shí),通知評(píng)論者的郵。

          插件下載地址:https://wordpress.org/plugins/wenprise-better-emails/


          主站蜘蛛池模板: 无码少妇一区二区三区浪潮AV | 91午夜精品亚洲一区二区三区| 无码人妻精品一区二区三| 性色av一区二区三区夜夜嗨 | 久久久综合亚洲色一区二区三区 | 日韩综合无码一区二区| 在线|一区二区三区| 日韩精品一区二区亚洲AV观看| 国产vr一区二区在线观看| 日韩精品一区二区三区中文版 | 亚洲高清一区二区三区| 亚洲成AV人片一区二区密柚| 亚洲AⅤ视频一区二区三区| 最新中文字幕一区二区乱码| 中文字幕av人妻少妇一区二区| 在线视频一区二区| 亚洲av无码不卡一区二区三区| 国产一区二区高清在线播放| 一区二区三区在线免费| 精品中文字幕一区二区三区四区| 无码少妇A片一区二区三区| 国产主播在线一区| 亚洲欧美日韩国产精品一区 | 精品伦精品一区二区三区视频| 国产91精品一区| 一区二区三区在线免费观看视频| 精品成人一区二区三区免费视频| 国产成人欧美一区二区三区| 无码日本电影一区二区网站| 亚洲高清一区二区三区| 久久久无码精品人妻一区| 国产精品一区二区资源| 精品一区二区三区在线观看l| 亚洲日韩一区精品射精| 国产欧美一区二区精品仙草咪| 精品国产免费观看一区| 无码人妻一区二区三区在线水卜樱 | 亚洲日韩一区精品射精| 午夜视频一区二区| 国产成人精品一区二三区| 亚洲国产高清在线一区二区三区 |