使用Django编写小型博客项目,更新完毕。

文章目录

  • 前言
  • 版本说明
  • 一. 新建项目
    • 1-1. 新建APP
    • 1-2. 建数据库模型
    • 1-3. 迁移数据库
    • 1-4. 选择数据库
  • 二. 使用Django操作数据
    • 2-1. 增加数据
    • 2-2. 查看数据
    • 2-3. 修改数据
    • 2-4. 删除数据
  • 三. 第一个页面
    • 3-1. 设置URL和视图函数
    • 3-2. 使用前端模板
  • 四. 使用css,cs以及模板
    • 4-1. 模板下载
    • 4-2. 更改视图函数
  • 五. 后台管理
    • 5-1. 注册用户和后台模型
    • 5-2. 注册APP
    • 5-3. 汉化优化后台管理
    • 5-4. 优化新建文章的表单
  • 六. 博客文章的详情页。
    • 6-1. 配置详情页的路由
    • 6-2. 设置详情页的模型
    • 6-3. 详情页的视图函数
    • 6-4. 基模板base.html
    • 6-5. 重写index.html
    • 6-6. 编写详情页detail.html
  • 七. 博客文章支持Markdown语法,和代码高亮
    • 7-1. 安装python Markdown
    • 7-2. 修改视图函数
    • 7-3. 添加示例文章
    • 7-4. safe方法
  • 八. Markdown 文章自动生成目录
    • 8-1. 在文中插入目录
    • 8-2. 在侧边栏插入目录并且判断是否为空
    • 8-3. 美化路由(锚点)
  • 九. 自动生成文章摘要
    • 9-1. 第一种方法
    • 9-2. 第二种方法
  • 十. 页面侧边栏,使用自定义模板标签
    • 10-1. 模板标签存放目录
    • 10-2. 编写"最新文章"模板标签
    • 10-3. 编写"归档"模板标签
    • 10-4. 编写"分类"模板标签
    • 10-5. 编写"标签云"模板标签
    • 10-6. 使用模板标签
  • 十一. 归档分类标签页面
    • 11-1. 归档页面
    • 11-2. 分类页面
    • 11-3. 标签页面
  • 十二. 评论功能
    • 12-1. 创建评论应用
    • 12-2. 设计评论的数据模型
    • 12-3. 把模型注册到Admin
    • 12-4. 设计评论表单
    • 12-5. 评论表单展示
    • 12-6. 评论的视图函数
    • 12-7. 发送评论消息
    • 12-8. 显示评论内容
  • 十三. 细节优化
    • 13-1. 博客文章默认排序
    • 13-2. 评论区域跳转和显示正确的评论量

前言

这是用Django写的一个小型的博客,重新学习下Django也给自己的学习留下点记忆吧。写的不是很好,但是都是我一字一句的手打出来了,希望大牛看见我的文章,轻喷,感谢。

版本说明

系统 Windows 10
python == 3.6.4
django == 2.2.3
Markdown == 3.1.1
后续用到什么我在补充

一. 新建项目

进入安装好Django的虚拟环境,并且进到项目存放目录中执行:

django-admin startproject Django_blog

目录结构如下:
使用Django编写小型博客项目,更新完毕。_第1张图片
修改本地时间及语言
在setting.py文件中找到以下两行代码进行修改

"""Django_blog/Django_blog/setting.py"""
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
# 把英文改为中文
LANGUAGE_CODE = 'zh-hans'
# 把国际时区改为中国时区(东八区)
TIME_ZONE = 'Asia/Shanghai'

我用的是pycharm直接运行就可以。或者直接进到项目目录执行:

python manage.py runserver

在这里插入图片描述
使用Django编写小型博客项目,更新完毕。_第2张图片
见到以上图片恭喜你成功了第一步。

1-1. 新建APP

进入到项目所在目录执行

python manage.py startapp blog

并且注册app
在setting.py文件的INSTALLED_APPS增加blog

"""Django_blog/Django_blog/setting.py"""

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog', # 注册 blog 应用
]

1-2. 建数据库模型

因为上一周事情比较多,博客就没有更新,现在继续更新,
设计思路:
分为了三张表,
博客分类:

ID 分类名
1 Django
2 Python

博客标签

ID 标签名
1 Django学习
2 Python学习

博客文章:

文章ID 标题 正文 发表时间 分类 标签
1 标题1 正文1 2019-11-10 Django Django学习
2 标题2 正文2 2019-11-11 Django Django学习
3 标题3 正文3 2019-11-11 Python Django学习

Django中比较方便的就是已经把数据库的语法,已经转换成Python语法,直接写Python代码就可以了,这是Django相当方便的地方:

# blog/models.py
from django.db import models
# Django后台管理系统的用户,不是自己定义的
from django.contrib.auth.models import User

# 博客分类
class Category(models.Model):
    name = models.CharField(max_length=100, verbose_name='分类名')

# 博客标签
class Tag(models.Model):
    name = models.CharField(max_length=100, verbose_name='标签名')

# 博客文章
class Post(models.Model):
    # 文章标题
    title = models.CharField(max_length=70, verbose_name='文章标题')
    # 正文
    body = models.TextField(verbose_name='文章正文')
    # 创建时间和修改时间
    created_time = models.DateTimeField(verbose_name='创建时间')
    modified_time = models.DateTimeField(verbose_name='修改时间')
    # 文章摘要 blank=True 允许为空
    excerpt = models.CharField(max_length=200, blank=True)
    # 分类 on_delete=models.CASCADE 参数是关联删除 一对多
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    # 标签 多对多,一个文章可以有多个标签,
    tags = models.ManyToManyField(Tag, blank=True)
    # 作者 一对多,一个作者可以写很多文章,
    author = models.ForeignKey(User, on_delete=models.CASCADE)

相关链接
Django模型内置字段类型
Django,一对多,多对多官方文档

1-3. 迁移数据库

在虚拟环境下执行以下两条命令,完成数据库迁移。

python manage.py makemigrations
python manage.py migrate

看见以下图片证明迁移成功,django 在 blog 应用的 migrations 目录下生成了一个 0001_initial.py 文件,这个文件是 django 用来记录我们对模型做了哪些修改的文件。
使用Django编写小型博客项目,更新完毕。_第3张图片
使用Django编写小型博客项目,更新完毕。_第4张图片
以下语句,可以查看得刚才在迁移数据库时Django做了什么。

python manage.py sqlmigrate blog 0001

1-4. 选择数据库

因为是自己搭建的一个小型的博客,所以这里的数据库使用SQLite3
如何想改成MySQL也是可以的。

blogproject/settings.py

DATABASES = {
    # sqlite3配置
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    
    # mysql配置
    # 'default': {
    #     'ENGINE': 'django.db.backends.mysql',
    #     'NAME': 'mysql_name',
    #     'USER': 'mysql_user',
    #     'PASSWORD': 'mysql_password',
    #     'HOST': 'mysql_host',
    #     'PORT': 'mysql_port',
    }
}

二. 使用Django操作数据

2-1. 增加数据

在manage.py所在目录下执行

python manage.py shell
>>> 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 赋了值。为了让 django 把这些数据保存进数据库,调用实例的 save 方法即可。
使用Django编写小型博客项目,更新完毕。_第5张图片
标签,分类都创建好了,还需要创建用户
输入quit()退出shell模式
输入命令创建用户

python manage.py createsuperuser

看见以下效果代表创建成功。注意输入密码的时候是什么都不会显示的,直接输入就可以。
使用Django编写小型博客项目,更新完毕。_第6张图片
接下来就是创建文章了。
再次进入shell

python manage.py shell

>>> from blog.models import Category, Tag, Post
>>> from django.utils import timezone
>>> from django.contrib.auth.models import User
>>> user = User.objects.get(username='xxxx') # 你刚才创建的名字
>>> 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。我们还导入了一个 django 提供的辅助模块 timezone,timezone模块相比于自带的datetime,多了对时区的处理。

2-2. 查看数据

还是在shell里面输入

>>> Category.objects.all()
>>> Tag.objects.all()
>>> Post.objects.all()

使用Django编写小型博客项目,更新完毕。_第7张图片
现在可读性很差,需要做的是在模型的类里定义__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

写好之后退出shell再次进入shell

python manage.py shell

>>> from blog.models import Category, Tag, Post
>>> Category.objects.all()
>>> Tag.objects.all()
>>> Post.objects.all()
>>> Post.objects.get(title='title test')

使用Django编写小型博客项目,更新完毕。_第8张图片

2-3. 修改数据

还是在shell里操作,不用退出shell,如果退出再重新导入下Category, Tag, Post,
使用get方法取到名为category test的分类,然后进行更改,更改之后使用save()保存,其他的也是一样修改

>>> c = Category.objects.get(name='category test')
>>> c.name = 'category test new'
>>> c.save()
>>> Category.objects.all()

使用Django编写小型博客项目,更新完毕。_第9张图片

2-4. 删除数据

还是在shell里操作,不用退出shell,如果退出再重新导入下Category, Tag, Post,

p = Post.objects.get(title='title test')
p.delete()
Post.objects.all()

先根据标题 title 的值从数据库中取出 Post,保存在变量 p 中,然后调用它的delete 方法,最后看到 Post.objects.all() 返回了一个空的 QuerySet(类似于一个列表),表明数据库中已经没有 Post,Post 已经被删除了。
使用Django编写小型博客项目,更新完毕。_第10张图片
Django增删查改官方文档

今天就更新到这里吧,2019/11/11,要去剁手了。

三. 第一个页面

3-1. 设置URL和视图函数

首先在blog下面的新建一个urls.py文件,新建之后的目录,目录结构如下:
使用Django编写小型博客项目,更新完毕。_第11张图片

#  blog\urls.py

from django.urls import path
from . import views
urlpatterns = [
    path('', views.index, name='index'),
]
"""第一个('')是网址,第二个(views.index)是处理函数,第三个是属于别名"""

接下来写视图函数

# blog\views.py

from django.http import HttpResponse
 
def index(request):
    return HttpResponse("欢迎访问我的博客首页!")

最后一步了,自己在app里写url了也写views了,但是还没在Django里配置,
在项目的目录的urls.py文件中配置,(也就是和settings.py)平级的urls.py里配置

# Django_blog/urls.py
"""这里面之前是有代码的,需要导入include然后在进行配置""""
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    # 配置blog路由
    path('', include('blog.urls')),
]

配置完直接运行Django项目,在访问就会在浏览器中看到

“欢迎来到我的博客首页”

如果看到这几个字,恭喜你,可以进行下一节的学习了。

3-2. 使用前端模板

在项目的根目录下新建一个名为“templates”,然后在templates下新建一个名为“blog”的文件夹,用来存放blog的模板,然后在blog文件夹下新建一个index.html
目录结构如下:
使用Django编写小型博客项目,更新完毕。_第12张图片
然后在index.html写下如下代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
</head>
<body>
<h1>{{ welcome }}</h1>
</body>
</html>

这里面用到了两处模板语法{{ title }}和{{ welcome }}
这两个值是可以直接在后端取得。

接下来是配置模板路径
settings.py 文件里找到TEMPLATES

# Django_blog/settings.py

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        # 这是初始的内容
        # 'DIRS': [],
        # 配置模板
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        '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',
            ],
        },
    },
]

第一个页面的最后一步在把视图函数改一下,因为之前的返回方式是没有使用到模板的,更改完使用模板的。

# blog/views.py
def index(request):
    # return HttpResponse('欢迎来到我的博客首页')
    # title和welcome直接传到index.html上直接使用
    context = {'title':'博客首页', 'welcome':'欢迎来到我的博客首页'}
    return render(request, 'blog/index.html', context)

使用Django编写小型博客项目,更新完毕。_第13张图片这就是第一个页面。
2019/11/13 隔了两天继续更新的,我不想把这一篇文章分为几个小的文章,我这也是刚开始学习写博客,写的不好的大牛多批评指教。

四. 使用css,cs以及模板

4-1. 模板下载

首先是需要下模板,模板地址:
Django模板下载地址
先在 blog 应用下建立一个 static 文件夹,再在 static 目录下建立一个 blog 文件夹,这是为了别的应用里也有同名的css和js文件。在把下载css和js文件夹和文件夹下面的文件一起复制到 \static\blog\里面
目录结构如下:
使用Django编写小型博客项目,更新完毕。_第14张图片
在把模板里的index.html替换掉你之前的index.html文件
现在启动不行的,需要把css和js的引用改一下

首先在第一行导入:{% load static %}

找到index.html文件的 “head” 标签

# 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>

带“+”的是修改后的,带“-”的是修改之前的,改完之后把带“-”的删除就行
现在也别着急,接下来使用模板语言渲染页面,

4-2. 更改视图函数

# blog\views.py

# 导入模型中的Post使用object查询
from .models import Post

def index(request):

    # 使用模板操作数据库,取到所有数据,然后使用order_by按插入时间排序。'后插入的显示在前面'
    post_list = Post.objects.all().order_by('-created_time')
    return render(request, 'blog/index.html', context={'post_list': post_list})

在index.html文件中找 “article” 标签,应该有四五个,留下一个就可以了。
修改如下:

# templates\blog\index.html
 
...
{% for post in post_list %}
  <article class="post post-{{ post.pk }}">
    ...
  </article>
{% empty %}
  <div class="no-post">暂时还没有发布的文章!</div>
{% endfor %}
...

# 还有替换的地方
...

<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>
···

这里有两个参数需要说一下
{% for post in post_list %}
和python中for循环很像,但是有{% %}包着,必须以{% endfor %}结尾,要不然会报错
{% empty %} 即数据库里没有文章时显示{% empty %} 下面的内容

替换完这些就可以运行项目了。

截止目前完整的index.html如下:

# 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="http://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.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="{% 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>
<div class="container">
    <header id="site-header">
        <div class="row">
            <div class="col-md-4 col-sm-5 col-xs-8">
                <div class="logo">
                    <h1><a href="index.html"><b>Black</b> &amp; White</a></h1>
                </div>
            </div><!-- col-md-4 -->
            <div class="col-md-8 col-sm-7 col-xs-4">
                <nav class="main-nav" role="navigation">
                    <div class="navbar-header">
                        <button type="button" id="trigger-overlay" class="navbar-toggle">
                            <span class="ion-navicon"></span>
                        </button>
                    </div>

                    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                        <ul class="nav navbar-nav navbar-right">
                            <li class="cl-effect-11"><a href="index.html" data-hover="首页">首页</a></li>
                            <li class="cl-effect-11"><a href="full-width.html" data-hover="博客">博客</a></li>
                            <li class="cl-effect-11"><a href="about.html" data-hover="关于">关于</a></li>
                            <li class="cl-effect-11"><a href="contact.html" data-hover="联系">联系</a></li>
                        </ul>
                    </div><!-- /.navbar-collapse -->
                </nav>
                <div id="header-search-box">
                    <a id="search-menu" href="#"><span id="search-icon" class="ion-ios-search-strong"></span></a>
                    <div id="search-form" class="search-form">
                        <form role="search" method="get" id="searchform" action="#">
                            <input type="search" placeholder="搜索" required>
                            <button type="submit"><span class="ion-ios-search-strong"></span></button>
                        </form>
                    </div>
                </div>
            </div><!-- col-md-8 -->
        </div>
    </header>
</div>
<div class="copyrights">Collect from <a href="http://www.cssmoban.com/">网页模板</a></div>
<div class="copyrights">Modified by <a href="http://zmrenwu.com/">yangzw的博客</a></div>

<div class="content-body">
    <div class="container">
        <div class="row">
            <main class="col-md-8">
                {% for post in post_list %}
                    <article class="post post-{{ post.pk }}">
                        <header class="entry-header">
                            <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>
                        </header>
                        <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>
                    </article>
                {% empty %}
                    <div class="no-post">暂时还没有发布的文章!</div>
                {% endfor %}
                <!-- 简单分页效果
                <div class="pagination-simple">
                    <a href="#">上一页</a>
                    <span class="current">6/11</span>
                    <a href="#">下一页</a>
                </div>
                -->
                <div class="pagination">
                    <ul>
                        <li><a href="">1</a></li>
                        <li><a href="">...</a></li>
                        <li><a href="">4</a></li>
                        <li><a href="">5</a></li>
                        <li class="current"><a href="">6</a></li>
                        <li><a href="">7</a></li>
                        <li><a href="">8</a></li>
                        <li><a href="">...</a></li>
                        <li><a href="">11</a></li>
                    </ul>
                </div>
            </main>
            <aside class="col-md-4">
                <div class="widget widget-recent-posts">
                    <h3 class="widget-title">最新文章</h3>
                    <ul>
                        <li>
                            <a href="#">Django 博客开发入门教程:前言</a>
                        </li>
                        <li>
                            <a href="#">Django 博客使用 Markdown 自动生成文章目录</a>
                        </li>
                        <li>
                            <a href="#">部署 Django 博客</a>
                        </li>
                    </ul>
                </div>
                <div class="widget widget-archives">
                    <h3 class="widget-title">归档</h3>
                    <ul>
                        <li>
                            <a href="#">20175</a>
                        </li>
                        <li>
                            <a href="#">20174</a>
                        </li>
                        <li>
                            <a href="#">20173</a>
                        </li>
                    </ul>
                </div>

                <div class="widget widget-category">
                    <h3 class="widget-title">分类</h3>
                    <ul>
                        <li>
                            <a href="#">Django 博客教程 <span class="post-count">(13)</span></a>
                        </li>
                        <li>
                            <a href="#">Python 教程 <span class="post-count">(11)</span></a>
                        </li>
                        <li>
                            <a href="#">Django 用户认证 <span class="post-count">(8)</span></a>
                        </li>
                    </ul>
                </div>

                <div class="widget widget-tag-cloud">
                    <h3 class="widget-title">标签云</h3>
                    <ul>
                        <li>
                            <a href="#">Django</a>
                        </li>
                        <li>
                            <a href="#">Python</a>
                        </li>
                        <li>
                            <a href="#">Java</a>
                        </li>
                        <li>
                            <a href="#">笔记</a>
                        </li>
                        <li>
                            <a href="#">文档</a>
                        </li>
                        <li>
                            <a href="#">AngularJS</a>
                        </li>
                        <li>
                            <a href="#">CSS</a>
                        </li>
                        <li>
                            <a href="#">JavaScript</a>
                        </li>
                        <li>
                            <a href="#">Snippet</a>
                        </li>
                        <li>
                            <a href="#">jQuery</a>
                        </li>
                    </ul>
                </div>
                <div class="rss">
                    <a href=""><span class="ion-social-rss-outline"></span> RSS 订阅</a>
                </div>
            </aside>
        </div>
    </div>
</div>
<footer id="site-footer">
    <div class="container">
        <div class="row">
            <div class="col-md-12">
                <p class="copyright">&copy 2017 - 采集自<a href="http://www.cssmoban.com/"
                                                        target="_blank" title="模板之家">模板之家</a>
                    -<a href="http://zmrenwu.com/" title="网页模板" target="_blank">yangzw的博客</a>修改
                </p>
            </div>
        </div>
    </div>
</footer>

<!-- Mobile Menu -->
<div class="overlay overlay-hugeinc">
    <button type="button" class="overlay-close"><span class="ion-ios-close-empty"></span></button>
    <nav>
        <ul>
            <li><a href="index.html">首页</a></li>
            <li><a href="full-width.html">博客</a></li>
            <li><a href="about.html">关于</a></li>
            <li><a href="contact.html">联系</a></li>
        </ul>
    </nav>
</div>

<script src="{% static 'blog/js/script.js' %}"></script>

</body>
</html>

启动项目之后看见如下图片:
使用Django编写小型博客项目,更新完毕。_第15张图片
恭喜你可以进行下一节的学习了。
因为现在后台数据库里还没有数据,所以不会显示。准备下一节插入数据:
2019/11/14

五. 后台管理

5-1. 注册用户和后台模型

在blog下的admin.py注测后台模型

# blog\admin.py

from django.contrib import admin
from .models import Post, Category, Tag
admin.site.register(Post)
admin.site.register(Category)
admin.site.register(Tag)

注册完之后启动项目访问 http://127.0.0.1:8000/admin/ 用你之前创建的用户登录就行
如果忘记之前创建的用户了。在虚拟环境中执行以下命令根据提示重新创建超级用户

python manage.py createsuperuser

使用Django编写小型博客项目,更新完毕。_第16张图片

5-2. 注册APP

我这是修改中文之后样子,你们不是也没关系。接下来进行设置。
blog文件夹下的apps.py

# blog\apps.py

class BlogConfig(AppConfig):
    name = 'blog'
    verbose_name = '博客'

然后在settings.py里进行注册,把之前的删除或者注释,要不然会报错。

# settings.py

NSTALLED_APPS = [
    'django.contrib.admin',
    ...
    'blog.apps.BlogConfig',  # 注册 blog 应用
]

5-3. 汉化优化后台管理

接下来是为模型model显示中文,只需在每个模型类下面添加一个Meta:

# blog\models.py

class Post(models.Model):
    ...
    author = models.ForeignKey(User, on_delete=models.CASCADE)
 
    class Meta:
        verbose_name = '文章'
        verbose_name_plural = verbose_name

我只举这一个例子,这三个模型类都是一样。
并且在模型类的每个字段里都写上verbose_name字段

# blog\models.py

class Post(models.Model):
    # 文章标题
    title = models.CharField(max_length=70, verbose_name='文章标题')
    # 正文
    body = models.TextField(verbose_name='文章正文')
    ···

现在基本上都是中文了,但是在后台文章列表中显示的信息太少了。
在这里插入图片描述
blog下的admin.py新写一个类,并注册

# blog\admin.py

class PostAdmin(admin.ModelAdmin):
    # 控制 Post 后台列表页展示的字段
    list_display = ['title', 'created_time', 'modified_time', 'category', 'author']
    
admin.site.register(Post, PostAdmin)
···

之后看着就好多了。
使用Django编写小型博客项目,更新完毕。_第17张图片

5-4. 优化新建文章的表单

控制表单字段还是在admin.py里面
自动填充作者信息

# blog\admin.py
class PostAdmin(admin.ModelAdmin):
    # 控制 Post 后台列表页展示的字段
    list_display = ['title', 'created_time', 'modified_time', 'category', 'author']
    # 控制后台表单展现的字段
    fields = ['title', 'body', 'excerpt', 'category', 'tags']
    # 自动填充作者信息
    def save_model(self, request, obj, form, change):
        obj.author = request.user
        super().save_model(request, obj, form, change)
···

自动填充默认时间
需要修改的是blog下的models.py需要添加一个字段和导入timezone模块
timezone可以自己处理时区,但是python自带的dete不可以。

# blog\models.py

from django.utils import timezone
 
class Post(models.Model):
    ...
    created_time = models.DateTimeField(verbose_name='创建时间', default=timezone.now)
    ...

最后修改时间和创建时间是不同的。不能用default,咱们可以对Post类的save方法进行重写。

# blog\models.py

from django.utils import timezone
 
class Post(models.Model):
   ...
 
    def save(self, *args, **kwargs):
        self.modified_time = timezone.now()
        super().save(*args, **kwargs)

完成这一步就可以看看效果了。后台效果和你也可以添加文章试一下。
现在的夜里真冷了,赶紧洗澡睡觉了。
2019/11/15

六. 博客文章的详情页。

6-1. 配置详情页的路由

blog下的urls.py配置

# blog\urls.py
blog/urls.py
 
from django.urls import path
 
 # 这行代码一定要写,要不然Django找不到是哪个app下的路由
app_name = 'blog'
urlpatterns = [
    path('', views.index, name='index'),
    path('posts//', views.detail, name='detail'),
]

6-2. 设置详情页的模型

那个"int:pk"我会在后面模型里备注解释
blog\models.py下修改代码

# blog\models.py

blog/models.py
 
···
from django.urls import reverse
···
 
class Post(models.Model):

    ...
    # 管理url记得导入serverse 第一个参数告诉Django找到blog下detail
    # 第二个参数把路由里的 替换为pk,例如/posts/2/
    def get_absolute_url(self):
        return reverse('blog:detail', kwargs={'pk': self.pk})
    ···

因为在路由写下detail视图函数了但是我们还没有。

6-3. 详情页的视图函数

blog下的views.py修改

# blog\views.py

from django.shortcuts import render, get_object_or_404
···

def index(request):
    ···
# 博客详情页,导入系统的404页面,如果文章存在就显示,不存在就返回404页面
def detail(request, pk):
    post = get_object_or_404(Post, pk=pk)
    return render(request, 'blog/detail.html', context={'post': post})

那么问题来了,视图函数也有了但是页面好像还没有没关系,我们把木下载的前端模板中的single.html拷贝到template\blog和index.html平级,然后改名为detail.html
在index.html上有两个显示详情页的a标签需要改下路由,才能进到详情页。
以下是需要更改的两个地方

# blog\index.html

<h1 class="entry-title">
	{#{{ post.title }}#}
	<a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
</h1>

{#继续阅读 #}
<a href="{{ post.get_absolute_url }}" class="more-link">继续阅读 <span class="meta-nav"></span></a>

考下来的每个页面都有很多相同的地方。所以我们可以把相同的地方提取出来做成一个模板,在改不同的地方

6-4. 基模板base.html

templates下新建一个base.html注意是和templates下的blog文件夹是平级的
然后在把index.html文件中的所有文件拷到base.html中
然后找到main标签替换成以下内容

# templates\base.html
 
...
<main class="col-md-8">
    {% block main %}
    {% endblock main %}
</main>
<aside class="col-md-4">
  {% block toc %}
  {% endblock toc %}
  ...
</aside>
...

6-5. 重写index.html

templates/blog/index.html
 
{% extends 'base.html' %}
 
{% block main %}
    {% for post in post_list %}
        <article class="post post-1">
          ...
        </article>
    {% empty %}
        <div class="no-post">暂时还没有发布的文章!</div>
    {% endfor %}
    <!-- 简单分页效果
    <div class="pagination-simple">
        <a href="#">上一页</a>
        <span class="current">6/11</span>
        <a href="#">下一页</a>
    </div>
    -->
    <div class="pagination">
      ...
    </div>
{% endblock main %}

{% extends ‘base.html’ %}代表继承base.html页面

{% block main %}
{% endblock main %}
{% block toc %}
{% endblock toc %}
这两个里面是可以变化的东西,属于定制化的,他们之外的都是不变的可以被继承。

6-6. 编写详情页detail.html

详情页也是继承base.html

# templates\blog\detail.html
 
{% extends 'base.html' %}
 
{% block main %}
    <article class="post post-1">
      ...
    </article>
    <section class="comment-area">
      ...
    </section>
{% endblock main %}
{% block toc %}
    <div class="widget widget-content">
        <h3 class="widget-title">文章目录</h3>
        <ul>
            <li>
                <a href="#">教程特点</a>
            </li>
            <li>
                <a href="#">谁适合这个教程</a>
            </li>
            <li>
                <a href="#">在线预览</a>
            </li>
            <li>
                <a href="#">资源列表</a>
            </li>
            <li>
                <a href="#">获取帮助</a>
            </li>
        </ul>
    </div>
{% endblock toc %}

这里面还是一些假数据呢,然后我们替换掉一部分内容。换成后台取出来的数据

# templates\blog\detail.html

<article class="post post-{{ post.pk }}">
  <header class="entry-header">
    <h1 class="entry-title">{{ post.title }}</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>
  </header>
  <div class="entry-content clearfix">
    {{ post.body }}
  </div>
</article>

在运行就是一下效果了,因为我删除了一篇文章,所以,我的路由是这个样子。
使用Django编写小型博客项目,更新完毕。_第18张图片
屏幕分辨率有点。。。哈哈,我该换电脑了。

七. 博客文章支持Markdown语法,和代码高亮

7-1. 安装python Markdown

在虚拟环境执行

pip install markdown

7-2. 修改视图函数

blog\views.py中,解析Markdown文章

# blog\views.py

···
import markdown
···

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',
                                  ])
    return render(request, 'blog/detail.html', context={'post': post})

下面是两篇可以简单学习Markdown语法的文章质量挺好的。
Markdown——入门指南
Markdown 语法说明 (简体中文版)

7-3. 添加示例文章

然后在admin后天管理系统添加带有Markdown语法的文章
因为语法的限制,那个Markdown代码那块需要自己更改下,标题和标签自己随意填写

# 一级标题
 
## 二级标题
 
### 三级标题
- 列表项1
- 列表项2
- 列表项3
 
> 这是一段引用


(```python)
给小括号去掉,然后在我写文字的部分拷贝一个函数进来。
(```)

使用Django编写小型博客项目,更新完毕。_第19张图片

7-4. safe方法

现在看详情还不行呢,因为我们还需要改一个地方,取消Django默认转义。
detail.html文件下找到{{ post.body }}标签,该问{{ post.body | safe }}
这下就只剩代码高亮了,之前Markdown导入过代码高亮,但是样式很丑,
所以需要导入前端插件

在haed头里面增加以下信息

# base.html

<head>
  ...
  <link href="https://cdn.bootcss.com/highlight.js/9.15.8/styles/github.min.css" rel="stylesheet">
 
  <style>
    .codehilite {
      padding: 0;
    }
 
    /* for block of numbers */
    .hljs-ln-numbers {
      -webkit-touch-callout: none;
      -webkit-user-select: none;
      -khtml-user-select: none;
      -moz-user-select: none;
      -ms-user-select: none;
      user-select: none;
 
      text-align: center;
      color: #ccc;
      border-right: 1px solid #CCC;
      vertical-align: top;
      padding-right: 5px;
    }
 
    .hljs-ln-n {
      width: 30px;
    }
 
    /* for block of code */
    .hljs-ln .hljs-ln-code {
      padding-left: 10px;
      white-space: pre;
    }
  </style>
</head>

在body标签里添加以下信息

# base.html

<body>
···
  <script src="https://cdn.bootcss.com/highlight.js/9.15.8/highlight.min.js"></script>
  <script src="https://cdn.bootcss.com/highlightjs-line-numbers.js/2.7.0/highlightjs-line-numbers.min.js"></script>
  <script>
    hljs.initHighlightingOnLoad();
    hljs.initLineNumbersOnLoad();
  </script>
···
</body>

再次打开就可以看见很美观的代码高亮了。
使用Django编写小型博客项目,更新完毕。_第20张图片
今天北京的天很冷,出去吃顿饭冻死我了都,但是代码写的我很开心。2019/11/16

八. Markdown 文章自动生成目录

8-1. 在文中插入目录

前一章我们已经把自动生成的模块导入到views.py里面了,直接在管理后台写一篇Markdown语法的博客文章。
我的文章内容如下:

@[toc]
# 主题一
标题一下面的正文Markdown
## 1-1
二标题内容1
### 1-1-1
三标题内容1
### 1-1-2
三标题内容2
## 1-2
二标题内容2
## 1-3
二标题内容3
### 这是标题三
标题三下的文字

效果如下:
使用Django编写小型博客项目,更新完毕。_第21张图片

8-2. 在侧边栏插入目录并且判断是否为空

首先需要把博客详情页的视图函数更改下。blog\views.py

# blog\views.py

import re
···


def detail(request, pk):
    post = get_object_or_404(Post, pk=pk)
    md = markdown.Markdown(extensions=[
        'markdown.extensions.extra',
        'markdown.extensions.codehilite',
        'markdown.extensions.toc',
    ])
    post.body = md.convert(post.body)
    # 判断目录是否为空
    m = re.search(r'
\s*
    (.*)
\s*
'
, md.toc, re.S) post.toc = m.group(1) if m is not None else '' return render(request, 'blog/detail.html', context={'post': post})

然后更好下html文件,templates\blog\detail.html
这里的模板语法和safe就不解释了,如果不明白去看上面的safe函数

# templates\blog\detail.html
···
{% block toc %}
  {% if post.toc %}
    <div class="widget widget-content">
      <h3 class="widget-title">文章目录</h3>
      <div class="toc">
        <ul>
          {{ post.toc | safe }}
        </ul>
      </div>
    </div>
  {% endif %}
{% endblock toc %}
···

效果如下,侧边也有了,文章里也有了,我点击的是主题一,路由显示的不是很友好。
使用Django编写小型博客项目,更新完毕。_第22张图片

8-3. 美化路由(锚点)

在详情页的是的视图函数里增加两个引用就行。\blog\views.py
记得引用要加上

# blog\views.py

···
from django.utils.text import slugify
from markdown.extensions.toc import TocExtension

def detail(request, pk):
	···
    md = markdown.Markdown(extensions=[
        'markdown.extensions.extra',
        'markdown.extensions.codehilite',
        # 'markdown.extensions.toc',
        # 美化锚点
        TocExtension(slugify=slugify),
    ])
    ···
    return render(request, 'blog/detail.html', context={'post': post})

来看下效果,
使用Django编写小型博客项目,更新完毕。_第23张图片
好看多了,今天就这样了不知不觉的又到了后半夜。
2019/11/18

九. 自动生成文章摘要

9-1. 第一种方法

重写save函数之前自动生成创建时间咱们已经重写过。
在正文Post模型中,body字段里面里面取指定多少个字符保存到excerpt字段里面,记得导入Markdown和strip_tags

# blog\models.py

···
import markdown
from django.utils.html import strip_tags
···

class Post(models.Model):
	···
    def save(self, *args, **kwargs):
        self.modified_time = timezone.now()
		# 首先实例化一个 Markdown 类,用于渲染 body 的文本。
        # 由于摘要并不需要生成文章目录,所以去掉了目录拓展。
        md = markdown.Markdown(extensions=[
            'markdown.extensions.extra',
            'markdown.extensions.codehilite',
        ])
        
        # 先将 Markdown 文本渲染成 HTML 文本
        # 在进行判断文章是否输入摘要
        # strip_tags 去掉 HTML 文本的全部 HTML 标签
        # 从文本摘取前 54 个字符赋给 excerpt
        if self.excerpt == None:
            self.excerpt = strip_tags(md.convert(self.body))[:54]
        super().save(*args, **kwargs)

然后index.html页面展示出来。

# templates\blog\index.html
 
<article class="post post-{{ post.pk }}">
  ...
  <div class="entry-content clearfix">
      <p>{{ post.excerpt }}...</p>
      <div class="read-more cl-effect-14">
          <a href="{{ post.get_absolute_url }}" class="more-link">继续阅读 <span class="meta-nav"></span></a>
      </div>
  </div>
</article>

接下来划重点,想要文章有摘要,一定要新添加一篇文章,随便写些文本上去测试下就可以。我的结果是这样的。
使用Django编写小型博客项目,更新完毕。_第24张图片

9-2. 第二种方法

使用 truncatechars 模板过滤器
这里的models.py不需要再改了,不需要再复写了。
直接用truncatechars模板过滤器

# templates\blog\index.html
 
<article class="post post-{{ post.pk }}">
  ...
  <div class="entry-content clearfix">
      <p>{{ post.body|truncatechars:54 }}...</p>
      <div class="read-more cl-effect-14">
          <a href="{{ post.get_absolute_url }}" class="more-link">继续阅读 <span class="meta-nav"></span></a>
      </div>
  </div>
</article>

纯文本文章可以,但是如果遇到了Markdown标题之列的会很难看,如下图
使用Django编写小型博客项目,更新完毕。_第25张图片
今天好像高烧了,要不然多更新点了,但是好难受啊,睡觉了。
2019/11/19

十. 页面侧边栏,使用自定义模板标签

内置的模板标签比如{% static %}或者{% for %} {% endfor%}
我们要写的是自定义模板标签,也可以说是定制标签,
下图圈出来的是写死的,今天要做的就是替换下图圈出的固定代码
使用Django编写小型博客项目,更新完毕。_第26张图片

10-1. 模板标签存放目录

首先需要在blog下创建templatetags的一个python文件夹,
然后在文件夹下创建一个__init__.py文件,让这个文件夹变成包,init.py里面不需要写东西。写上这个就变成python的包了。
并且在这个文件夹下创建blog_extras.py文件用于编写模板标签逻辑
目录结构如下
使用Django编写小型博客项目,更新完毕。_第27张图片

10-2. 编写"最新文章"模板标签

blog_extras.py文件下写入以下代码
首先引入Django的template,然后实例化template.Library类,
并且用装饰器register.inclusion_tag装饰
show_recent_posts函数,

 # blog\templatetags\blog_extras.py
 
from django import template
 
from ..models import Post, Category, Tag
 
register = template.Library()
 
 # 最新文章模板标签
'''
takes_context 设置为 True 时将告诉 django,
在渲染 _recent_posts.html 模板时,
不仅传入show_recent_posts 返回的模板变量,
同时会传入父模板(即使用 {% show_recent_posts %} 模板标签的模板)
上下文(可以简单理解为渲染父模板的视图函数传入父模板的模板变量以及 django 自己传入的模板变量)。
当然这里并没有用到这个上下文,这里只是做个简单演示,如果需要用到,就可以在模板标签函数的定义中使用
context 变量引用这个上下文。
'''
@register.inclusion_tag('blog/inclusions/_recent_posts.html', takes_context=True)
def show_recent_posts(context, num=5):
    return {'recent_post_list': Post.objects.all().order_by('-created_time')[:num],}

如果用pycharm写的话那个路径是报错的,这下就该写html页面了,
templates\blog下创建inclusions文件夹,用于存放标模板标签的html在inclusions文件夹下创建 _recent_posts.html 文件

# templates\blog\inclusions\_recent_posts.html

<div class="widget widget-recent-posts">
  <h3 class="widget-title">最新文章</h3>
  <ul>
    {% for post in recent_post_list %}
      <li>
        <a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
      </li>
    {% empty %}
      暂无文章!
    {% endfor %}
  </ul>
</div>

10-3. 编写"归档"模板标签

和最新文章类似,先写逻辑在写html页面

# blog\templatetags\blog_extras.py

# 归档模板标签
'''
这里 Post.objects.dates 方法会返回一个列表,
列表中的元素为每一篇文章(Post)的创建时间(已去重),
且是 Python 的 date 对象,精确到月份,降序排列。
接受的三个参数值表明了这些含义,一个是 created_time ,
即 Post 的创建时间,month 是精度,order='DESC' 表明降序排列(即离当前越近的时间越排在前面)
。例如我们写了 3 篇文章,分别发布于 2019 年 2 月 21 日、2019 年 3 月 25 日、2019 年 3 月 28 日
,那么 dates 函数将返回 2019 年 3 月 和 2019 年 2 月这样一个时间列表,且降序排列,从而帮助我们实现按月归档的目的。
'''
@register.inclusion_tag('blog/inclusions/_archives.html', takes_context=True)
def show_archives(context):
    return {'date_list': Post.objects.dates('created_time', 'month', order='DESC'),}

现在编写页面,在inclusions文件夹下创建 _archives.html 文件

# templates\blog\inclusions\_archives.html

<div class="widget widget-archives">
  <h3 class="widget-title">归档</h3>
  <ul>
    {% for date in date_list %}
      <li>
        <a href="#">{{ date.year }}{{ date.month }}</a>
      </li>
    {% empty %}
      暂无归档!
    {% endfor %}
  </ul>
</div>

10-4. 编写"分类"模板标签

逻辑

# blog\templatetags\blog_extras.py

@register.inclusion_tag('blog/inclusions/_categories.html', takes_context=True)
def show_categories(context):
    return {'category_list': Category.objects.all(), }

html页面,在inclusions文件夹下创建 _categories.html 文件

# templates\blog\inclusions\_categories.html
<div class="widget widget-category">
  <h3 class="widget-title">分类</h3>
  <ul>
    {% for category in category_list %}
      <li>
        <a href="#">{{ category.name }} <span class="post-count">(13)</span></a>
      </li>
    {% empty %}
      暂无分类!
    {% endfor %}
  </ul>
</div>

(11)那个11目前还不是动态的,后面会介绍。

10-5. 编写"标签云"模板标签

逻辑

# blog\templatetags\blog_extras.py

# 标签云模板标签
@register.inclusion_tag('blog/inclusions/_tags.html', takes_context=True)
def show_tags(context):
    return {'tag_list': Tag.objects.all(),}

html页面,在inclusions文件夹下创建 _tags.html 文件

# templates\blog\inclusions\_tags.html

<div class="widget widget-tag-cloud">
  <h3 class="widget-title">标签云</h3>
  <ul>
    {% for tag in tag_list %}
      <li>
        <a href="#">{{ tag.name }}</a>
      </li>
    {% empty %}
      暂无标签!
    {% endfor %}
  </ul>
</div>

10-6. 使用模板标签

首选先导入自定义模板标签

# templates\base.html
 
{% load static %}
{% load blog_extras %}
<!DOCTYPE html>
<html>
...
</html>

然后找都侧边栏进行替换

# templates\base.html
 
<aside class="col-md-4">
  {% block toc %}
  {% endblock toc %}
 
  {% show_recent_posts %}
  {% show_archives %}
  {% show_categories %}
  {% show_tags %}
 
  <div class="rss">
     <a href=""><span class="ion-social-rss-outline"></span> RSS 订阅</a>
  </div>
</aside>

然后打开index.html删除之前写死的数据
也就是
{% block toc %}
这里的代码全部删除
{% endblock %}
现在运行下看下效果就可以了。
使用Django编写小型博客项目,更新完毕。_第28张图片
上周回老家了,停了几天没更新,估计还有最多两周也就更新完了,还是有收获的。2019/11/25

十一. 归档分类标签页面

上一章我们写了侧边栏,但是只有一个最新文章是可以点的,其余的归档分类和标签都是不能正常显示内容的。今天我们就把剩下的工作完成

11-1. 归档页面

记住一个顺序,是我自己喜欢的顺序,先写视图函数,然后在写路由,最后写html页面,分类页面和标签页面都是按照这个顺序来。
咱们就按照这个来,先写视图函数。

# blog\views.py

···
def archive(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', context={'post_list': post_list})

视图函数完事是路由

 # blog\urls.py
 
 urlpatterns = [
    ···
    path('posts//', views.detail, name='detail'),
    # 归档路由
    path('archives///', views.archive, name='archive'),
]

最后是html页面

# templates\blog\inclusions\_archives.html
...
{% for date in date_list %}
<li>
  <a href="{% url 'blog:archive' date.year date.month %}">
    {{ date.year }}{{ date.month }}</a>
</li>
{% endfor %}
...

写完归档启动项目测试下吧。归档页面我截下图,分类页面和标签页面我就不截图了,一块写完一起启动测试吧。
注意我圈起来的地方,点击完归档路由是有变化的。
使用Django编写小型博客项目,更新完毕。_第29张图片
使用Django编写小型博客项目,更新完毕。_第30张图片

11-2. 分类页面

这三个都是类似的,我就不一一介绍了,代码里有注释。
分类视图函数

# blog\views.py

from .models import Post, Category, Tag

···
# 分类视图函数
def category(request, pk):
    # 先导入404页面,记得导入Category类,和Tag类,提前做准备
    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})

分类路由

 # blog\urls.py
 
 urlpatterns = [
    ···
    # 分类路由
    path('categories//', views.category, name='category'),
]

分类html页面

# templates\blog\inclusions\_categories.html

...
{% for category in category_list %}
<li>
  <a href="{% url 'blog:category' category.pk %}">{{ category.name }}</a>
</li>
{% endfor %}
...

11-3. 标签页面

标签视图函数

# blog\views.py

···
# 标签视图函数
def tag(request, pk):
    t = get_object_or_404(Tag, pk=pk)
    post_list = Post.objects.filter(tags=t).order_by('-created_time')
    return render(request, 'blog/index.html', context={'post_list': post_list})

标签路由

 # blog\urls.py
 
 urlpatterns = [
    ···
    # 标签路由
    path('tag//', views.tag, name='tag')
]

标签html页面

# templates\blog\inclusions\_tags.html

...
{% for tag in tag_list %}
  <li>
    <a href="{% url 'blog:tag' tag.pk %}">{{ tag.name }}</a>
  </li>
{% empty %}
    暂无标签!
{% endfor %}
...

侧边栏的功能基本实现了,明天要去我朋友那,周末要回老家,所以今天可能是这周更新的最后一篇。
2019/11/27

十二. 评论功能

12-1. 创建评论应用

在虚拟环境下执行以下命令创建应用。应用名叫comments

python manage.py startapp comments

创建完之后注册app

# blogproject\settings.py
 
...
INSTALLED_APPS = [
    ...
   'blog.apps.BlogConfig',  # 后台注册博客
   'comments.apps.CommentsConfig',  # 注册 comments 应用
]
...

然后在admin后台显示中文名称

# comments\app.py
 
from django.apps import AppConfig
 
class CommentsConfig(AppConfig):
    name = 'comments'
    verbose_name = '评论'

目录结构截图如下:
使用Django编写小型博客项目,更新完毕。_第31张图片

12-2. 设计评论的数据模型

直接上代码吧,这个模型的字段之前都解释过是,应该很好理解,如果还有不懂得直接去看一. 新建项目的的 1-2创建数据模型

# comments\models.py

from django.db import models
from django.utils import timezone


class Comment(models.Model):
    name = models.CharField(max_length=50, verbose_name='名字')
    email = models.EmailField(verbose_name='邮箱')
    # blank=True允许为空
    url = models.URLField(blank=True, verbose_name='网址')
    text = models.TextField(verbose_name='内容')
    created_time = models.DateTimeField(verbose_name='创建时间', default=timezone.now)
    # 设置外键
    post = models.ForeignKey('blog.Post', verbose_name='文章', on_delete=models.CASCADE)

    class Meta:
        verbose_name = '评论'
        verbose_name_plural = verbose_name

    def __str__(self):
        return '{}: {}'.format(self.name, self.text[:20])

然后数据库模型迁移,进入到虚拟环境中,依次执行以下两条命令

python manage.py makemigrations
python manage.py migrate

使用Django编写小型博客项目,更新完毕。_第32张图片

12-3. 把模型注册到Admin

还是直接上代码,如何记得不是很清楚了去看五. 后台管理下的5-3. 汉化后台管理

# 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)

启动项目查看下效果如下。
使用Django编写小型博客项目,更新完毕。_第33张图片
明天更新评论的表单,今天就先写到这吧,上周更新的很少,争取这周多更新点。2019/12/2
看了下已经两周没有更新了。今天才更新的。

12-4. 设计评论表单

表单通俗的讲就是前用户在页面上输入的,需要保存,所以用表单的形式进行提交到后端。
comments 目录下新建一个 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']

12-5. 评论表单展示

这个就之前的自定义标签一样了,可以回顾下第十章。
comments 文件夹下新建一个 templatetags 包里面__init__.py
然后在templatetags里面创建comments_extras.py文件和__init__.py平级
目录结构如下图
使用Django编写小型博客项目,更新完毕。_第34张图片
comments_extras.py代码如下:

# comments\templatetags\comments_extras.py

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, }

然后在 templates/comments/inclusions 目录下新建一个 _form.html 模板,写上代码:

# 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>
</form>

接下来就是下修改详情页代码,显示空表单

# templates\blog\detail.html

{% extends 'base.html' %}
{% load comments_extras %}
...
 
<h3>发表评论</h3>
{% show_comment_form post %}

这还不能显示得配置下路由才可以。
在 comment 应用中再建一个 urls.py 文件,写入代码:

# comments\urls.py

from django.urls import path
from . import views

app_name = 'comments'
urlpatterns = [
    path('comment/', views.comment, name='comment'),
]

然后在项目的urls.py里进行配置

# \Django_blog\urls.py
from django.contrib import admin
from django.urls import path, include
 
urlpatterns = [
    ···
    # 配置评论路由
    path('', include('comments.urls')),
]

现在启动项目打开博客详情页就可以看见空表的表单了。
使用Django编写小型博客项目,更新完毕。_第35张图片

12-6. 评论的视图函数

上面只是显示一个空白的表单但是还不能提交。也不能录入到数据库了,设计下视图函数实现这个功能。
comments应用的views.py里面写逻辑

# comments\views.py

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):
    # 先获取被评论的文章,因为后面需要把评论和被评论的文章关联起来。
    # 这里我们使用了 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)

有视图函数就有HTML页面
templates下的comments下新建preview.html

# templates\comments\preview.html

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

12-7. 发送评论消息

视图函数写完了,接下来就是发送消息了,并且储存到数据库
在视图函数中增加以下几行代码。

# comments\views.py
# 导入Django自带的messages相关配置
from django.contrib import messages
if form.is_valid():
    ...
    comment.save()
 	# 评论成功之后直接给个提示消息
    messages.add_message(request, messages.SUCCESS, '评论发表成功!', extra_tags='success')
    return redirect(post)
    
context = {
        'post': post,
        'form': form,
    }
# 评论失败也会给个提示消息
messages.add_message(request, messages.ERROR, '评论发表失败!请修改表单中的错误后重新提交。', extra_tags='danger')

然后在base.html增加以下几行代码。这段代码是在body里面的。如果找不到位置,可以参考我GitHub上的源码。

# 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"><span
                aria-hidden="true">&times;</span></button>
        {{ message }}
      </div>
    {% endfor %}
{% endif %}

以下是测试效果。使用Django编写小型博客项目,更新完毕。_第36张图片

12-8. 显示评论内容

也是使用自定义模板标签
comments_extras.py 下增加一个自定义的模板标签

# comments\templatetags\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,
    }

然后在comments下的inclusions下新建 _list.html显示评论列表

# 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

# templates\blog\detail.html

<h3>发表评论</h3>
{% show_comment_form post %}
<div class="comment-list-panel">
    {% show_comments post %}
</div>

效果如下图
使用Django编写小型博客项目,更新完毕。_第37张图片
一周多都没有更新了,事太多了,这周事可能少一点了,争取明天把博客更新完,就还剩下一点点了。2019/12/16

十三. 细节优化

13-1. 博客文章默认排序

这个就比较简单了。直接上代码就可以看的懂。

# blog\models.py
class Post(models.Model):
    ...
    ...
    class Meta:
        verbose_name = '文章'
        verbose_name_plural = verbose_name
        # 对文章进行排序,后插入的文章显示在最上面
        ordering = ['-created_time']

13-2. 评论区域跳转和显示正确的评论量

index.html下修改以下代码。
修改完以下代码就可以index页就可以显示正常的评论量了,但是详情页还不行。

# templates\blog\index.html
<div class="entry-meta">
    ...
    <span class="comments-link"><a href="{{ post.get_absolute_url }}#comment-area">{{ post.comment_set.count }} 评论</a></span>
    <span class="views-count"><a href="{{ post.get_absolute_url }}">{{ post.views }} 阅读</a></span>
</div>

detail.html下修改代码,使详情页也可以显示正确的评论量。

# templates\blog\detail.html

<header class="entry-header">
    <h1 class="entry-title">{{ post.title }}</h1>
        ...
       <span class="comments-link"><a href="#comment-area">{{ post.comment_set.count }} 评论</a></span>
       <span class="views-count"><a href="#">{{ post.views }} 阅读</a></span>
    </div>
</header>

至此Django写的一个小型的博客编写完成,经理了很长的时间,但是这其中我的收获也是很多的。最后在附上我的源码地址吧,
源码地址:https://github.com/Buddhistwang/Django_blog

你可能感兴趣的:(Django,项目学习及实战,Django,博客,python3,自学,项目)