文章目錄
- 0.思路引導
- 1.創建"評論"應用
- 2.設計"評論"的數據庫模型
- 3.注冊"評論"模型到 admin
- 4.設計“評論”表單
- 5.展示評論表單
- 6.“評論”視圖函數
- 7.綁定 URL
- 8.向讀者發送是否“評論”成功的狀態
- 9.詳情頁底部顯示“評論”內容
0.思路引導
本文將創建一個新的應用,即在博客詳情頁的底部,增加評論功能。
效果顯示如下所示,具體的操作將從頭到尾進行記錄。
1.創建"評論"應用
1)進入到項目根目錄,然后輸入如下命令創建一個新的應用:
pipenv run python manage.py startapp comments
2)在 settings.py 里注冊這個應用,django 才知道這是一個應用
文件位置:blogproject/settings.py
...
INSTALLED_APPS = [...'blog.apps.BlogConfig', # 注冊 blog 應用'comments.apps.CommentsConfig', # 注冊 comments 應用
]v
...
3)注意這里注冊的是 CommentsConfig 類,同時進行配置,讓 comment 應用在 django 的 admin 后臺顯示中文名字。
文件位置:comments/app.py
from django.apps import AppConfigclass CommentsConfig(AppConfig):name = 'comments'verbose_name = '評論'
2.設計"評論"的數據庫模型
1)編寫數據庫模型文件
文件位置:comments/models.py
from django.db import models
from django.utils import timezoneclass 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_namedef __str__(self):return '{}: {}'.format(self.name, self.text[:20])
2)遷移數據庫
在項目根目錄下分別運行下面兩條命令:
pipenv run python manage.py makemigrations
pipenv run python manage.py migrate
3.注冊"評論"模型到 admin
既然已經創建了模型,我們就可以將它注冊到 django admin 后臺,方便管理員用戶對評論進行管理
文件位置:comments/admin.py
from django.contrib import admin
from .models import Commentclass CommentAdmin(admin.ModelAdmin):list_display = ['name', 'email', 'url', 'post', 'created_time']fields = ['name', 'email', 'url', 'text', 'post']admin.site.register(Comment, CommentAdmin)
實際效果展示如下:
4.設計“評論”表單
思維邏輯:
1)表單是用來收集并向服務器提交用戶輸入的數據的,是用戶在我們博客網站上發表評論的過程。
2)當用戶想要發表評論時,他找到我們給他展示的一個評論表單(在文章詳情頁的底部就有一個評論表單),然后根據表單的要求填寫相應的數據。
3)之后用戶點擊評論按鈕,這些數據就會發送給某個 URL。我們知道每一個 URL 對應著一個 django 的視圖函數,于是 django 調用這個視圖函數,我們在視圖函數中,對用戶通過表單提交上來的數據進行處理,比如驗證數據的合法性并且保存數據到數據庫中,那么用戶的評論就被 django 處理了。
4)如果通過表單提交的數據存在錯誤,那么我們把錯誤信息返回給用戶,并在前端重新渲染表單,要求用戶根據錯誤信息修正表單中不符合格式的數據,再重新提交。
文件位置:comments/forms.py
from django import forms
from .models import Commentclass CommentForm(forms.ModelForm):class Meta:model = Commentfields = ['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 需要顯示。
5.展示評論表單
表單類已經定義完畢,現在的任務是在文章的詳情頁下方將這個表單展現給用戶,用戶可以通過這個表單填寫評論數據,從而發表評論。
那么怎么展現一個表單呢?django 會根據表單類的定義自動生成表單的 HTML 代碼,我們要做的就是實例化這個表單類,然后將表單的實例傳給模板,讓 django 的模板引擎來渲染這個表單。那怎么將表單類的實例傳給模板呢?
1)因為表單出現在文章詳情頁,一種想法是修改文章詳情頁 detail 視圖函數,在這個視圖中實例化一個表單,然后傳遞給模板。然而這樣做的一個缺點就是需要修改 detail 視圖函數的代碼,而且 detail 視圖函數的作用主要就是處理文章詳情;
2)一個視圖函數最好不要讓它做太多雜七雜八的事情。另外一種想法是使用自定義的模板標簽,我們在 頁面側邊欄曾經使用自定義模板標簽,這里我們使用自定義模板標簽的方法,來渲染表單頁面。
在 comments\ 文件夾下新建一個 templatetags 文件夾,然后創建 init.py 文件使其成為一個包,再創建一個 comments_extras.py 文件用于存放模板標簽的代碼。然后我們定義一個 inclusion_tag 類型的模板標簽,用于渲染評論表單。
文件位置:comments\comments_extras.py
from django import template
from ..forms import CommentFormregister = 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>
然后我們就可以在 detail.html 中使用這個模板標簽來渲染表單了,注意在使用前記得先 {% load comments_extras %} 這個模塊。而且為了避免可能的報錯,最好重啟一下開發服務器。
{% extends 'base.html' %}
{% load comments_extras %}
...<h3>發表評論</h3>
{% show_comment_form post %}
6.“評論”視圖函數
當用戶提交表單中的數據后,django 需要調用相應的視圖函數來處理這些數據,下面開始寫我們視圖函數處理邏輯:
from blog.models import Post
from django.shortcuts import get_object_or_404, redirect, render
from django.views.decorators.http import require_POSTfrom .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 頁面,來展示表單中的錯誤,以便用戶修改后重新提交。
文件位置:templates/comments/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 格式不合法,表單校驗了數據格式,然后渲染錯誤提示:
7.綁定 URL
在 comment 應用中再建一個 urls.py 文件,寫上 URL 模式:
文件位置: comments/urls.py
from django.urls import pathfrom . import viewsapp_name = 'comments'
urlpatterns = [path('comment/<int:post_pk>', views.comment, name='comment'),
]
然后在項目的 blogproject\ 目錄的 urls.py 里包含 comments\urls.py 這個文件:
文件位置:blogproject/urls.py
from django.contrib import admin
from django.urls import path,includeurlpatterns = [path(r'admin/', admin.site.urls),path(r'', include('blog.urls')),path(r'', include('comments.urls')),
]
8.向讀者發送是否“評論”成功的狀態
第六部分中,測試提交評論功能時,嘗試輸入非法格式的數據,例如將郵箱輸入為 xxx@xxx,那么評論視圖在校驗表單數據合法性時,發現郵箱格式不符,就會渲染 preview 頁面,展示表單中的錯誤,將郵箱修改為正確的格式后,再次點擊發表,頁面就跳轉到了被評論文章的詳情頁,說明視圖正確執行了保存表單數據到數據庫的邏輯。
不過這里有一點不好的地方就是,評論成功后頁面直接跳轉到了被評論文章的詳情頁,沒有任何提示,用戶也不知道評論究竟有沒有真的成功。這里我們使用 django 自帶的 messages 應用來給用戶發送評論成功或者失敗的消息。
django 默認已經為我們做好了 messages 的相關配置,直接用即可。
兩個地方需要發送消息,第一個是當評論成功,即評論數據成功保存到數據庫后,因此在 comment 視圖中加一句。
from django.contrib import messagesif 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 的導航條代碼下增加如下代碼:
文件位置:templates/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"><spanaria-hidden="true">×</span></button>{{ message }}</div>{% endfor %}
{% endif %}
這里 django 會通過全局上下文自動把 messages 變量傳給模板,這個變量里存儲我們發送的消息內容,然后就是循環顯示消息了。這里我們使用了 bootstrap 的一個 alert 組件,為其設置不同的 class 會顯示不同的顏色,所以之前添加消息時傳入的 extra_tags 就派上了用場。比如這里 alert-{{ message.tags }},當傳入的是 success 時,類名就為 alert-success,這時顯示的消息背景顏色就是綠色,傳入的是 dangerous,則顯示的就是紅色。
評論發布成功和失敗的消息效果如下圖:
9.詳情頁底部顯示“評論”內容
為了不改動已有的視圖函數的代碼,評論數據我們也使用自定義的模板標簽來實現。
文件位置:comments\comments_extras.py
@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,}
文件位置: templates/comments/inclusions/_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>
最后在detail.html 中,將此前占位用的評論模板替換為模板標簽渲染的內容:
<h3>發表評論</h3>
{% show_comment_form post %}
<div class="comment-list-panel">{% show_comments post %}
</div>
訪問文章詳情頁,即可看到已經發表的評論列表。效果展示如下: