Django搭建blog網站(2)

来源:https://www.cnblogs.com/derek1184405959/archive/2018/03/15/8570623.html
-Advertisement-
Play Games

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.yeardate.month 的值。例如 blog:archives 表示 blog 應用下的 archives 函數,這個函數對應的 URL 模式為 ^archives/(?P<year>[0-9]{4})/(?P<month>[0-9]{1,2})/$,假設 date.year=2018date.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.完善跳轉鏈接

導航欄有一個

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • function requestAlarmLiu() { $.post("alarm/getPersonAlarmList", {disposeStatus: 'NOT'}, function (data) { var sosCount = 0; var healthCount = 0; for ( ...
  • 效果圖: html部分: JQuery部分: ...
  • 由於項目的要求,需要給覆選框設置樣式,初始樣式:,第一次點擊的時候顯示,第二次點擊時候需要改變該樣式:。 設計思路: 當點擊次數為奇數時顯示帶有顏色的圖片 當點擊次數為偶數時顯示沒有顏色的圖片 下邊是我封裝的一個函數; ...
  • 在知乎上看到一個關於“泛基“的實現,感覺挺有意思,想試試效果,代碼如下: 先忽略這段代碼的作用,重點是運行後控制台沒有任何輸出。跟蹤一下發現根本沒有走MyClass(),DataForThisType的值一直是null。關於靜態構造方法,網上的解釋是: 通常情況下:最先調用基類的構造方法,但如果該類 ...
  • 今天我們要談論微服務以及如何使用Nginx構建一個快速的、安全的網路系統。最後,我們將向您展示一個使用Fabric模式如何非常快速和輕鬆地構建一個微服務的demo。 在我們探討Fabric模式之前,我想談一談微服務並且從Nginx的角度來看這意味著什麼。 0:56 - 大轉變 微服務已經引起了應用程 ...
  • 什麼是分散式系統 分散式系統是由一組通過網路進行通信、為了完成共同的任務而協調工作的電腦節點組成的系統。分散式系統的出現是為了用廉價的、普通的機器完成單個電腦無法完成的計算、存儲任務。其目的是利用更多的機器,處理更多的數據。 首先需要明確的是,只有當單個節點的處理能力無法滿足日益增長的計算、存儲 ...
  • Facade模式也叫外觀模式,是由GoF提出的23種設計模式中的一種。Facade模式為一組具有類似功能的類群,比如類庫,子系統等等,提供一個一致的簡單的界面。這個一致的簡單的界面被稱作facade。 外觀模式的角色和職責 1、Facade:為調用方定義簡單的調用介面。 2、Clients:調用者。 ...
  • 經典的Java面試題(第二部分),這部分主要是與Java Web和Web Service相關的面試題。 96、闡述Servlet和CGI的區別? 答:Servlet與CGI的區別在於Servlet處於伺服器進程中,它通過多線程方式運行其service()方法,一個實例可以服務於多個請求,並且其實例一 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...