通過建立部落格學習Django-1
本篇文章主要對追夢人物的部落格:使用 django 開發一個個人部落格進行總結
詳情參考:https://www.zmrenwu.com/courses/hellodjango-blog-tutorial/
部署環境
https://www.zmrenwu.com/courses/hellodjango-blog-tutorial/materials/59/
安裝Python
當前python版本為3.6.4
訪問Python官網進行Python版本的下載,下載具體步驟可參考python安裝過程或者百度一下找到合適的教程
使用虛擬環境
當前使用的是 Pipenv 建立和管理虛擬環境
- 安裝 Pipenv
pip install pipenv
- 建立資料夾作為專案的根目錄,在當前資料夾執行
pipenv install
- 啟用虛擬環境,在專案根目錄下執行
pipenv shell
命令
安裝Django
當前使用Django版本為2.2.3
在專案根目錄下執行
pipenv install django==2.2.3
測試安裝是否成功,在專案根目錄下輸入命令
> pipenv run python (HelloDjango-blog-tutorial-VDQF8f6V) > python Python 3.6.4 (v3.6.4:d48eceb, Dec 19 2017, 06:54:40) [MSC v.1900 64 bit (AMD6Type "help", "copyright", "credits" or "license" for more information. >>> import django >>> print(django.get_version()) 2.2.3
安裝好Django後,推薦使用pycharm進行操作
建立Django工程
pipenv run django-admin startproject blogproject C:\Users\yangxg\SpaceLocal\Workspace\G_Courses\HelloDjango-blog-tutorial
django-admin startproject
命令用來初始化一個 django 專案,接收兩個引數,第一個是專案名 blogproject,第二個指定專案生成的位置,因為之前我們為了使用 Pipenv 建立了專案根目錄,所以將專案位置指定為此前建立的位置。
Hello Django
在專案根目錄下執行 pipenv run python manage.py runserver
命令就可以在本機上開啟一個 Web 伺服器
> pipenv run python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
You have 17 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
July 05, 2019 - 21:05:37
django version 2.2.3, using settings 'blogproject.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
結束命令 按Ctrl+z後,再點選Enter結束,或者按 Ctrl+c
部落格應用
https://www.zmrenwu.com/courses/hellodjango-blog-tutorial/materials/60/
建立部落格應用
專案根目錄下執行以下命令建立blog應用
pipenv run python manage.py startapp blog
應用的目錄結構
檢視根目錄新增的blog應用目錄
blog\
__init__.py
admin.py
apps.py
migrations\
__init__.py
models.py
tests.py
views.py
將 blog 應用新增settings.py檔案中
HelloDjango-blog-tutorial/blogproject/settings.py
## 其他配置項...
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog', # 註冊 blog 應用
]
## 其他配置項...
建立 Django 部落格的資料庫模型
https://www.zmrenwu.com/courses/hellodjango-blog-tutorial/materials/61/
設計部落格的資料庫表結構
分類和標籤的資料庫表
分類 id | 分類名 |
---|---|
1 | Django |
2 | Python |
標籤 id | 標籤名 |
---|---|
1 | Django 學習 |
2 | Python 學習 |
編寫部落格模型程式碼
在models.py編寫文章(Post)、分類(Category)以及標籤(Tag)對應的 Python 類
blog/models.py
from django.db import models
from django.contrib.auth.models import User
# 分類 -----------------------------------------------------------------
class Category(models.Model):
name = models.CharField(max_length=100)
# 標籤 -----------------------------------------------------------------
class Tag(models.Model):
name = models.CharField(max_length=100)
# 文章 -----------------------------------------------------------------
class Post(models.Model):
# 文章標題
title = models.CharField(max_length=70)
# 文章正文
body = models.TextField()
# 建立時間和修改時間
created_time = models.DateTimeField()
modified_time = models.DateTimeField()
# 文章摘要
excerpt = models.CharField(max_length=200, blank=True)
# 分類和標籤
category = models.ForeignKey(Category, on_delete=models.CASCADE)
tags = models.ManyToManyField(Tag, blank=True)
# 文章作者
author = models.ForeignKey(User, on_delete=models.CASCADE)
部落格模型程式碼程式碼詳解
Category 和 Tag 類
Category
和Tag
類,都繼承models.Model
類(django 規定)Category
和Tag
類都有一個name
屬性,用來儲存它們的名稱- 使用
CharField
指定name
的資料型別為字串 max_length
引數指定name
允許的最大長度
Post類
-
必須繼承自
models.Model
類 -
title
:文章的標題,資料型別是CharField
,允許的最大長度max_length = 70
-
body
:文章正文,使用TextField
。使用CharField
儲存比較短的字串,使用TextField
來儲存大段文字 -
created_time
、modified_time
:文章的建立時間和最後一次修改時間,儲存時間的列用DateTimeField
資料類 -
excerpt
:文章摘要,可以沒有文章摘要,但預設情況下CharField
要求我們必須存入資料,否則就會報錯。指定CharField
的blank=True
引數值後就可以允許空值 -
category
和tags
:分類與標籤,一篇文章只能對應一個分類,但是一個分類下可以有多篇文章,使用
ForeignKey
,一對多的關聯關係,ForeignKey
必須傳入一個on_delete
引數用來指定當關聯的資料被刪除時,被關聯的資料的行為(假定當某個分類被刪除時,該分類下全部文章也同時被刪除),使用models.CASCADE
引數,意為級聯刪除。一篇文章可以有多個標籤,同一個標籤下也可能有多篇文章,使用
ManyToManyField
,多對多的關聯關係。同時我們規定文章可以沒有標籤,因此為標籤 tags 指定了blank=True
-
author
:文章作者,這裡User
是從 django.contrib.auth.models 匯入的。(django.contrib.auth 是 django 內建的應用,專門用於處理網站使用者的註冊、登入等流程。)User
是 django 為我們已經寫好的使用者模型,和我們自己編寫的Category
等類是一樣的。這裡我們通過ForeignKey
把文章和User
關聯了起來,因為我們規定一篇文章只能有一個作者,而一個作者可能會寫多篇文章,因此這是一對多的關聯關係,和Category
類似。
更多欄位型別查詢: django 官方文件
理解多對一和多對多兩種關聯關係
ForeignKey
文章 ID | 標題 | 正文 | 分類 ID |
---|---|---|---|
1 | title 1 | body 1 | 1 |
2 | title 2 | body 2 | 1 |
3 | title 3 | body 3 | 1 |
4 | title 4 | body 4 | 2 |
分類 ID | 分類名 | |
---|---|---|
1 | Django | |
2 | Python |
ManyToManyField
文章 ID | 標題 | 正文 |
---|---|---|
1 | title 1 | body 1 |
2 | title 2 | body 2 |
3 | title 3 | body 3 |
4 | title 4 | body 4 |
標籤 ID | 標籤名 |
---|---|
1 | Django 學習 |
2 | Python 學習 |
文章 ID | 標籤 ID |
---|---|
1 | 1 |
1 | 2 |
2 | 1 |
3 | 2 |
Django 遷移、操作資料庫
https://www.zmrenwu.com/courses/hellodjango-blog-tutorial/materials/62/
遷移資料庫
切換到 manage.py 檔案所在的目錄(專案根目錄)下,分別執行 pipenv run python manage.py makemigrations
和 pipenv run python manage.py migrate
命令:
> pipenv run python manage.py makemigrations
Migrations for 'blog':
blog\migrations\0001_initial.py
- Create model Category
- Create model Tag
- Create model Post
> pipenv run python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying auth.0010_alter_group_name_max_length... OK
Applying auth.0011_update_proxy_permissions... OK
Applying blog.0001_initial... OK
Applying sessions.0001_initial... OK
執行 python manage.py makemigrations
後,django 在 blog 應用的 migrations 目錄下生成了一個 0001_initial.py 檔案,是 django 用來記錄對模型修改情況的檔案。目前我們在 models.py 檔案裡建立了 3 個模型類,django 把這些變化記錄在了 0001_initial.py 裡
選擇資料庫版本
在沒有安裝任何的資料庫軟體情況下,django 幫我們遷移了資料庫。這是因為我們使用了 Python 內建的 SQLite3 資料庫,專案根目錄下多出了一個 db.sqlite3 的檔案
django 在 settings.py 裡為我們做了一些預設的資料庫配置:
blogproject/settings.py
## 其它配置選項...
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
## 其它配置選項...
用 django 的方式操作資料庫 CURD
存資料
建立一個分類和一個標籤:
>>> from blog.models import Category, Tag, Post
>>> c = Category(name='category test')
>>> c.save()
>>> t = Tag(name='tag test')
>>> t.save()
- 匯入 3 個之前寫好的模型類
- 例項化了一個
Category
類和一個Tag
類,為他們的屬性name
賦值 - 呼叫例項的
save
方法讓 django 把這些資料儲存進資料庫
建立User:
User 用於指定文章的作者,執行 pipenv run python manage.py createsuperuser
命令並根據提示建立使用者:
> pipenv run python manage.py createsuperuser
使用者名稱 (leave blank to use 'yangxg'): admin
電子郵件地址: [email protected]
Password: # 密碼輸入過程中不會有任何字元顯示
Password (again):
Superuser created successfully.
建立一篇文章:
執行 python manage.py shell
進入 Python 命令互動欄,開始建立文章:
>>> from blog.models import Category, Tag, Post
>>> from django.utils import timezone
>>> from django.contrib.auth.models import User
>>> user = User.objects.get(username='myuser')
>>> c = Category.objects.get(name='category test')
>>> p = Post(title='title test', body='body test', created_time=timezone.now(), modified_time=timezone.now(), category=c, author=user)
>>> p.save()
- 重啟了 shell,需要重新匯入
Category
、Tag
、Post
以及User
- 匯入輔助模組
timezone
,呼叫now()
方法為created_time
和modified_time
指定時間,now
方法返回當前時間 - 根據使用者名稱和分類名,通過
get
方法取出了存在資料庫中的User
和Category
- 為文章指定
title
、body
、created_time
、modified_time
值,並把它和前面建立的 Category 以及 User 關聯起來 - 允許為空
excerpt
、tags
沒有為它們指定值
取資料
>>> Category.objects.all()
<QuerySet [<Category: Category object>]>
>>> Tag.objects.all()
<QuerySet [<Tag: Tag object>]>
>>> Post.objects.all()
<QuerySet [<Post: Post object>]>
>>>
objects
模型管理器,使用all
方法,表示把對應的資料全部取出來
為顯示資料更加人性化,將3個模型分別增加一個 __str__
方法:
blog/models.py
class Category(models.Model):
...
def __str__(self):
return self.name
class Tag(models.Model):
...
def __str__(self):
return self.name
class Post(models.Model):
...
def __str__(self):
return self.title
- 定義好後
Category
返回分類名name
,Tag
返回標籤名,Post
返回它的title
重新執行 python manage.py shell
進入 Shell:
>>> from blog.models import Category, Tag, Post
>>> Category.objects.all()
<QuerySet [<Category: category test>]>
>>> Tag.objects.all()
<QuerySet [<Tag: tag test>]>
>>> Post.objects.all()
<QuerySet [<Post: title test>]>
>>> Post.objects.get(title='title test')
<Post: title test>
all
方法返回全部資料,是一個類似於列表的資料結構(QuerySet)get
返回一條記錄資料,如有多條記錄或者沒有記錄,get
方法均會丟擲相應異常
改資料
>>> c = Category.objects.get(name='category test')
>>> c.name = 'category test new'
>>> c.save()
>>> Category.objects.all()
<QuerySet [<Category: test category new>]>
- 通過
get
方法根據分類名name
獲取值為 category test 到分類 - 修改它的
name
屬性為新的值 category test new - 呼叫
save
方法把修改儲存到資料庫 - 檢視資料庫返回的資料已經是修改後的值(
Tag
、Post
的修改同category
)
刪資料
>>> p = Post.objects.get(title='title test')
>>> p
<Post: title test>
>>> p.delete()
(1, {'blog.Post_tags': 0, 'blog.Post': 1})
>>> Post.objects.all()
<QuerySet []>
- 根據標題
title
的值從資料庫中取出Post
,儲存在變數p
中 - 呼叫它的
delete
方法, - 檢視
Post.objects.all()
返回了一個空的 QuerySet(類似於一個列表),表明資料庫中已經沒有 Post,Post 已經被刪除了
Django 的接客之道
https://www.zmrenwu.com/courses/hellodjango-blog-tutorial/materials/63/
Django 處理 HTTP 請求
瀏覽器知道訪問指定網址後,在後臺主要把我們的訪問意圖包裝成一個 HTTP 請求,發給我們想要訪問的網址所對應的伺服器。
通俗點說就是瀏覽器幫我們通知網站的伺服器,說有人來訪問你啦,訪問的請求都寫在 HTTP 報文裡了,你按照要求處理後告訴我,我再幫你迴應他
Hello 檢視函式
繫結 URL 與檢視函式
在 blog 應用的目錄下建立一個 urls.py 檔案,目錄為:
blog\
__init__.py
admin.py
apps.py
migrations\
0001_initial.py
__init__.py
models.py
tests.py
views.py
urls.py
在 blog\urls.py 中寫入:
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
]
- 從 django.urls 匯入了
path
函式,從當前目錄下匯入了 views 模組 - 把網址和處理函式的關係寫在了
urlpatterns
列表裡
繫結關係的寫法:
- 把網址和對應的處理函式作為引數傳給
path
函式(第一個引數是網址,第二個引數是處理函式), - 還傳遞另一個引數
name
,引數的值將作為處理函式index
的別名 - 當用戶輸入開發的網址 http://127.0.0.1:8000 後,django 首先會把協議 http、域名 127.0.0.1 和埠號 8000 去掉,此時只剩下一個空字串,
''
的模式正是匹配一個空字串,於是二者匹配,django 便會呼叫其對應的views.index
函式
編寫檢視函式
檢視函式定義在 views.py 檔案裡:
blog/views.py
from django.http import HttpResponse
def index(request):
return HttpResponse("歡迎訪問我的部落格首頁!")
Web 伺服器的作用:接收來自使用者的 HTTP 請求,根據請求內容作出相應的處理,並把處理結果包裝成 HTTP 響應返回給使用者
- 接收
request
的引數(request
是 django 為我們封裝好的 HTTP 請求,它是類HttpRequest
的一個例項) - 返回 HTTP 響應給使用者(HTTP 響應也是 django 幫我們封裝好的,它是類
HttpResponse
的一個例項,只是我們給它傳了一個自定義的字串引數)
配置專案 URL
將 blog 應用下的 urls.py 檔案包含到 blogproject\urls.py 裡去,開啟這個檔案看到如下內容:
blogproject/urls.py
"""
一大段註釋
"""
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]
修改成如下的形式:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('blog.urls')),
]
- 匯入
include
函式,把 blog 應用下的 urls.py 檔案包含進來 - include 前有一個
''
,這是一個空字串。這裡也可以寫其它字串,django 會把這個字串和後面 include 的 urls.py 檔案中的 URL 拼接
執行結果
執行 pipenv run python manage.py runserver
開啟開發伺服器
歡迎訪問我的部落格首頁!
使用 django 模板系統
- 根目錄下建立一個 templates 資料夾,用來存放模板
- 在 templates 目錄下建立 blog 資料夾,用來存放和 blog 應用相關的模板
- 在 templates\blog 目錄下建立一個名為 index.html 的檔案
HelloDjango-blog-tutorial\
manage.py
...
templates\
blog\
index.html
在 templates\blog\index.html 檔案裡寫入:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
</head>
<body>
<h1>{{ welcome }}</h1>
</body>
</html>
{{ title }}
,{{ welcome }}
:用 {{ }} 包起來的變數叫做模板變數- django 在渲染這個模板的時候會根據我們傳遞給模板的變數替換掉這些變數,最終在模板中顯示的將會是我們傳遞的值
在 settings.py 檔案裡設定模板檔案所在的路徑:
blogproject/settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.djangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
DIRS
設定模板的路徑,在 [] 中寫入 os.path.join(BASE_DIR, 'templates')
:
blogproject/settings.py
TEMPLATES = [
{
...
'DIRS': [os.path.join(BASE_DIR, 'templates')],
...
},
]
BASE_DIR
是 settings.py 在配置開頭前面定義的變數,記錄的是工程根目錄 HelloDjango-blog-tutorial 的值,在這個目錄下有模板檔案所在的目錄 templates\- 用
os.path.join
把BASE_DIR
和templates
兩個路徑連線,構成完整的模板路徑,django 就知道去這個路徑下面找我們的模板了
修改檢視函式:
blog/views.py
from django.shortcuts import render
def index(request):
return render(request, 'blog/index.html', context={
'title': '我的部落格首頁',
'welcome': '歡迎訪問我的部落格首頁'
})
不直接把字串傳給 HttpResponse
,而是呼叫render
函式,根據傳入的引數來構造 HttpResponse
- 把 HTTP 請求傳進去
render
根據第二個引數的值 blog/index.html 找到這個模板檔案並讀取模板中的內容render
根據傳入的context
引數的值把模板中的變數替換為我們傳遞的變數的值{{ title }}
被替換成了context
字典中title
對應的值,{{ welcome }}
也被替換成相應的值- HTML 模板中的內容字串被傳遞給
HttpResponse
物件並返回給瀏覽器
部落格從“裸奔”到“有面板”
首頁檢視函式
blog/views.py
from django.shortcuts import render
from .models import Post
def index(request):
post_list = Post.objects.all().order_by('-created_time')
return render(request, 'blog/index.html', context={'post_list': post_list})
- 使用
all()
方法從資料庫裡獲取了全部的文章,存在了post_list
變數裡 all
方法返回的是一個QuerySet
(可以理解成一個類似於列表的資料結構)order_by
方法對這個返回的QuerySet
進行排序- 排序依據的欄位是
created_time
,即文章的建立時間 -
號表示逆序,如果不加-
則是正序- 渲染了 blog\index.html 模板檔案,並且把包含文章列表資料的
post_list
變數傳給了模板
處理靜態檔案
在 blog 應用下建立 static 資料夾,為避免衝突,在 static 目錄下建立 blog 資料夾,把下載的部落格模板的全部檔案拷貝進這個目錄
blog\
__init__.py
static\
blog\
css\
.css 檔案...
js\
.js 檔案...
admin.py
apps.py
migrations\
__init__.py
models.py
tests.py
views.py
用下載的部落格模板中的 index.html 檔案替換掉之前我們自己寫的 index.html 檔案
正確引入 static 檔案下的 CSS 和 JavaScript 檔案:
templates/blog/index.html
+ {% load static %}
<!DOCTYPE html>
<html>
<head>
<title>Black & White</title>
<!-- meta -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- css -->
- <link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="http://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
- <link rel="stylesheet" href="css/pace.css">
- <link rel="stylesheet" href="css/custom.css">
+ <link rel="stylesheet" href="{% static 'blog/css/bootstrap.min.css' %}">
+ <link rel="stylesheet" href="{% static 'blog/css/pace.css' %}">
+ <link rel="stylesheet" href="{% static 'blog/css/custom.css' %}">
<!-- js -->
- <script src="js/jquery-2.1.3.min.js"></script>
- <script src="js/bootstrap.min.js"></script>
- <script src="js/pace.min.js"></script>
- <script src="js/modernizr.custom.js"></script>
+ <script src="{% static 'blog/js/jquery-2.1.3.min.js' %}"></script>
+ <script src="{% static 'blog/js/bootstrap.min.js' %}"></script>
+ <script src="{% static 'blog/js/pace.min.js' %}"></script>
+ <script src="{% static 'blog/js/modernizr.custom.js' %}"></script>
</head>
<body>
<!-- 其它內容 -->
- <script src="js/script.js' %}"></script>
+ <script src="{% static 'blog/js/script.js' %}"></script>
</body>
</html>
- 表示刪掉這一行, + 表示增加這一行
{% %}
包裹起來的叫做模板標籤,功能類似於函式,例如這裡的static
模板標籤,它把跟在後面的字串'css/bootstrap.min.css'
轉換成正確的檔案引入路徑{{ }}
包裹起來的叫做模板變數,作用是在最終渲染的模板裡顯示由檢視函式傳過來的變數值
修改模板
在模板 index.html 中你會找到一系列 article 標籤:
templates/blog/index.html
...
<article class="post post-1">
...
</article>
<article class="post post-2">
...
</article>
<article class="post post-3">
...
</article>
...
使用 {% for %} 模板標籤,將 index.html 中多餘的 article 標籤刪掉,只留下一個 article 標籤:
templates/blog/index.html
...
{% for post in post_list %}
<article class="post post-{{ post.pk }}">
...
</article>
{% empty %}
<div class="no-post">暫時還沒有釋出的文章!</div>
{% endfor %}
...
- {% empty %} 的作用是當
post_list
為空,即資料庫裡沒有文章時顯示 {% empty %} 下面的內容 - {% endfor %} 告訴 django 迴圈在這裡結束了
在迴圈體內通過 post
變數訪問單篇文章的資料
<h1 class="entry-title">
<a href="single.html">Adaptive Vs. Responsive Layouts And Optimal Text Readability</a>
</h1>
<div class="entry-meta">
<span class="post-category"><a href="#">django 部落格教程</a></span>
<span class="post-date"><a href="#"><time class="entry-date"
datetime="2012-11-09T23:15:57+00:00">2017年5月11日</time></a></span>
<span class="post-author"><a href="#">追夢人物</a></span>
<span class="comments-link"><a href="#">4 評論</a></span>
<span class="views-count"><a href="#">588 閱讀</a></span>
</div>
<!-- 摘要 -->
<div class="entry-content clearfix">
<p>免費、中文、零基礎,完整的專案,基於最新版 django 1.10 和 Python 3.5。帶你從零開始一步步開發屬於自己的部落格網站,幫助你以最快的速度掌握 django
開發的技巧...</p>
<div class="read-more cl-effect-14">
<a href="#" class="more-link">繼續閱讀 <span class="meta-nav">→</span></a>
</div>
</div>
替換成 post
的 title
屬性值
<h1 class="entry-title">
<a href="single.html">{{ post.title }}</a>
</h1>
<div class="entry-meta">
<span class="post-category"><a href="#">{{ post.category.name }}</a></span>
<span class="post-date"><a href="#"><time class="entry-date"
datetime="{{ post.created_time }}">{{ post.created_time }}</time></a></span>
<span class="post-author"><a href="#">{{ post.author }}</a></span>
<span class="comments-link"><a href="#">4 評論</a></span>
<span class="views-count"><a href="#">588 閱讀</a></span>
</div>
<!-- 摘要 -->
<div class="entry-content clearfix">
<p>{{ post.excerpt }}</p>
<div class="read-more cl-effect-14">
<a href="#" class="more-link">繼續閱讀 <span class="meta-nav">→</span></a>
</div>
</div>