通过创建博客学习Django-1

本篇文章主要对追梦人物的博客《使用 django 开发一个个人博客》进行总结



https://www.zmrenwu.com/courses/hellodjango-blog-tutorial/materials/59/

开始进入 django 开发之旅

安装Python

当前python版本为3.6.4

访问Python官网进行Python版本的下载,下载具体步骤可参考python安装过程或者百度一下找到合适的教程

使用虚拟环境

当前使用的是 Pipenv 创建和管理虚拟环境

  • 安装 Pipenvpip 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:UsersyangxgSpaceLocalWorkspaceG_CoursesHelloDjango-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 应用
]
 
## 其他配置项...

https://www.zmrenwu.com/courses/hellodjango-blog-tutorial/materials/61/

创建 Django 博客的数据库模型

设计博客的数据库表结构

分类和标签的数据库表

分类 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 类

  • CategoryTag 类,都继承 models.Model 类(django 规定)
  • CategoryTag 类都有一个name 属性,用来存储它们的名称
  • 使用 CharField 指定 name 的数据类型为字符串
  • max_length 参数指定 name 允许的最大长度

Post类

  • 必须继承自 models.Model

  • title:文章的标题,数据类型是 CharField,允许的最大长度 max_length = 70

  • body:文章正文,使用 TextField。使用 CharField存储比较短的字符串,使用 TextField 来存储大段文本

  • created_timemodified_time:文章的创建时间和最后一次修改时间,存储时间的列用 DateTimeField 数据类

  • excerpt:文章摘要,可以没有文章摘要,但默认情况下 CharField 要求我们必须存入数据,否则就会报错。指定 CharFieldblank=True 参数值后就可以允许空值

  • categorytags:分类与标签,

    一篇文章只能对应一个分类,但是一个分类下可以有多篇文章,使用 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

https://www.zmrenwu.com/courses/hellodjango-blog-tutorial/materials/62/

Django 迁移、操作数据库

迁移数据库

切换到 manage.py 文件所在的目录(项目根目录)下,分别运行 pipenv run python manage.py makemigrationspipenv run python manage.py migrate 命令:

> pipenv run python manage.py makemigrations
Migrations for 'blog':
  blogmigrations001_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
电子邮件地址: admin@example.com
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,需要重新导入 CategoryTagPost 以及 User
  • 导入辅助模块 timezone,调用 now() 方法为 created_timemodified_time 指定时间, now 方法返回当前时间
  • 根据用户名和分类名,通过 get 方法取出了存在数据库中的 UserCategory
  • 为文章指定 titlebodycreated_timemodified_time值,并把它和前面创建的 Category 以及 User 关联起来
  • 允许为空 excerpttags 没有为它们指定值

取数据

>>> 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 返回分类名 nameTag 返回标签名,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 方法把修改保存到数据库
  • 查看数据库返回的数据已经是修改后的值(TagPost 的修改同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 已经被删除了

https://www.zmrenwu.com/courses/hellodjango-blog-tutorial/materials/63/

Django 的接客之道

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

在 blogurls.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 文件包含到 blogprojecturls.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 应用相关的模板
  • 在 templateslog 目录下建立一个名为 index.html 的文件
HelloDjango-blog-tutorial
    manage.py
    ...
    templates
        blog
            index.html

在 templateslogindex.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.joinBASE_DIRtemplates两个路径连接,构成完整的模板路径,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 对象并返回给浏览器

https://www.zmrenwu.com/courses/hellodjango-blog-tutorial/materials/64/

博客从“裸奔”到“有皮肤”

首页视图函数

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,即文章的创建时间
  • - 号表示逆序,如果不加 - 则是正序
  • 渲染了 blogindex.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 &amp; 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>

替换成 posttitle 属性值

<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>
原文地址:https://www.cnblogs.com/chungzhao/p/13304869.html