作者:HelloGitHub-追梦人物

文中涉及的示例代码,已同步更新到 HelloGitHub-Team 仓库

在此之前我们完成了 django 博客首页视图的编写,我们希望首页展示发布的博客文章列表,但是它却抱怨:暂时还没有发布的文章!如它所言,我们确实还没有发布任何文章,本节我们将使用 django 自带的 admin 后台来发布我们的博客文章。

创建 admin 后台管理员账户

要想进入django admin 后台,首先需要创建一个超级管理员账户。我们在 Django 迁移、操作数据库 中已经创建了一个后台账户,但如果你没有按照前面的步骤创建账户的话,可以进入项目根目录,运行 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.

注意:

在命令行输入密码时可能不会显示输入的字符,不要以为键盘坏了,照正常的方式输入密码即可。

在 admin 后台注册模型

要在后台注册我们自己创建的几个模型,这样 django admin 才能知道它们的存在,注册非常简单,只需要在 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/ ,就进入了到了django admin 后台登录页面,输入刚才创建的管理员账户密码就可以登录到后台了。

可以看到我们刚才注册的三个模型了,点击 Posts 后面的增加按钮,将进入添加 Post 的页面,也就是新增博客文章。然后在相关的地方输入一些测试用的内容,增加完后点击保存,这样文章就添加完毕了,你也可以多添加几篇看看效果。注意每篇文章必须有一个分类,在添加文章时你可以选择已有分类。如果数据库中还没有分类,在选择分类时点击 Category 后面的 + 按钮新增一个分类即可。

你可能想往文章内容中添加图片,但目前来说还做不到。在支持 Markdown 语法部分中将介绍如何在文章中插入图片的方法。

访问 http://127.0.0.1:8000/ 首页,你就可以看到你添加的文章列表了,下面是我所在环境的效果图:

定制 admin 后台

使用 admin 后台的时候,我们发现了下面的一些体验相关的问题:

  • admin 后台本身的页面元素是已经汉化了的,但是我们自己的 blog 应用,以及 Post、Category、Tag 在页面中显示却是英文的,以及发布文章的时候,表单各字段的 label 也是英文的。
  • 在 admin 后台的 post 列表页面,我们只看到了文章的标题,但是我们希望它显示更加详细的信息,例如作者、发布时间、修改时间等。
  • 新增文章时,所有数据都要自己手动填写。但是,有些数据应该是自动生成。例如文章发布时间 created_time 和修改时间 modified_time,应该在创建或者修改文章时自动生成,而不是手动控制。同时我们的博客是单人博客系统,发布者肯定是文章作者,这个也应该自动设定为 admin 后台的登录账户。

虽然 django 的 admin 应用开箱即用,但也提供了丰富的定制功能,这正是 django 吸引人的地方,下面我们根据需求来一个个定制。

汉化 blog 应用

首先来看一下需要汉化的地方,admin 首页每个版块代表一个 app,比如 BLOG 版块表示 blog 应用,版块标题默认显示的就是应用名。应用版块下包含了该应用全部已经注册到 admin 后台的 model,之前我们注册了 Post、Category 和 Tag,所以显示的是这三个 model,显示的名字就是 model 的名字。如下图所示:

其次是新增 post 页面的表单,各个字段的 label 由定义在 Post 类的 Field 名转换而来,比如 Post 模型中定义了 title 字段,则对应表单的 label 就是 Title。

首先是 BLOG 版块的标题 BLOG,一个版块代表一个应用,显然这个标题使用应用名转换而来,在 blog 应用下有一个 app.py 模块,其代码如下:

from django.apps import AppConfig

class BlogConfig(AppConfig):
name = 'blog'

这些是我们在运行 startapp 创建 blog 应用时自动生成的代码,可以看到有一个 BlogConfig 类,其继承自 AppConfig 类,看名字就知道这是和应用配置有关的类。我们可以通过设置这个类中的一些属性的值来配置这个应用的一些特性的。比如这里的 name 是用来定义 app 的名字,需要和应用名保持一致,不要改。要修改 app 在 admin 后台的显示名字,添加 verbose_name 属性。

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

同时,我们此前在 settings 中注册应用时,是直接注册的 app 名字 blog,现在在 BlogConfig 类中对 app 做了一些配置,所以应该将这个类注册进去:

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

再次登录后台,就可以看到 BLOG 版块的标题已经显示为博客了。

接下来是让应用下注册的 model 显示为中文,既然应用是在 apps.py 中配置,那么和 model 有关的配置应该去找相对应的 model 。配置 model 的一些特性是通过 model 的内部类 Meta 中来定义。比如对于 Post 模型,要让他在 admin 后台显示为中文,如下:

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

同样地,这里通过 verbose_name 来指定对应的 model 在 admin 后台的显示名称,这里 verbose_name_plural 用来表示多篇文章时的复数显示形式。英语中,如果有多篇文章,就会显示为 Posts,表示复数,中文没有复数表现形式,所以定义为和 verbose_name 一样。

同样的可以把 Tag 和 Category 也设置一下:

class Category(models.Model):
name = models.CharField(max_length=100) class Meta:
verbose_name = '分类'
verbose_name_plural = verbose_name def __str__(self):
return self.name class Tag(models.Model):
name = models.CharField(max_length=100) class Meta:
verbose_name = '标签'
verbose_name_plural = verbose_name def __str__(self):
return self.name

在 admin 就可以看到汉化后的效果了。

然后就是修改 post 的表单的 label,label 由定义在 model 中的 Field 名转换二来,所以在 Field 中修改。

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, verbose_name='分类', on_delete=models.CASCADE)
tags = models.ManyToManyField(Tag, verbose_name='标签', blank=True)
author = models.ForeignKey(User, verbose_name='作者', on_delete=models.CASCADE)

可以看到我们给每个 Field 都传入了一个位置参数,参数值即为 field 应该显示的名字(如果不传,django 自动根据 field 名生成)。这个参数的名字也叫 verbose_name,绝大部分 field 这个参数都位于第一个位置,但由于 ForeignKeyManyToManyField 第一个参数必须传入其关联的 Model,所以 category、tags 这些字段我们使用了关键字参数 verbose_name

文章列表显示更加详细的信息

在 admin 后台的文章列表页面,我们只看到了文章的标题,但是我们希望它显示更加详细的信息,这需要我们来定制 admin 了,在 admin.py 添加如下代码:

blog/admin.py

from django.contrib import admin
from .models import Post, Category, Tag class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'created_time', 'modified_time', 'category', 'author'] # 把新增的 Postadmin 也注册进来
admin.site.register(Post, PostAdmin)
admin.site.register(Category)
admin.site.register(Tag)

刷新 admin Post 列表页面,可以看到显示的效果好多了。

简化新增文章的表单

接下来优化新增文章时,填写表单数据的不合理的地方。文章的创建时间和修改时间应该根据当前时间自动生成,而现在是由人工填写,还有就是文章的作者应该自动填充为后台管理员用户,那么这些自动填充数据的字段就不需要在新增文章的表单中出现了。

此前我们在 blog/admin.py 中定义了一个 PostAdmin 来配置 Post 在 admin 后台的一些展现形式。list_display 属性控制 Post 列表页展示的字段。此外还有一个 fields 属性,则用来控制表单展现的字段,正好符合我们的需求:

class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'created_time', 'modified_time', 'category', 'author']
fields = ['title', 'body', 'excerpt', 'category', 'tags']

这里 fields 中定义的字段就是表单中展现的字段。

接下来是填充创建时间,修改时间和文章作者的值。之前提到,文章作者应该自动设定为登录后台发布此文章的管理员用户。发布文章的过程实际上是一个 HTTP 请求过程,此前提到,django 将 HTTP 请求封装在 HttpRequest 对象中,然后将其作为第一个参数传给视图函数(这里我们没有看到新增文章的视图,因为 django admin 已经自动帮我们生成了),而如果用户登录了我们的站点,那么 django 就会将这个用户实例绑定到 request.user 属性上,我们可以通过 request.user 取到当前请求用户,然后将其关联到新创建的文章即可。

Postadmin 继承自 ModelAdmin,它有一个 save_model 方法,这个方法只有一行代码:obj.save()。它的作用就是将此 Modeladmin 关联注册的 model 实例(这里 Modeladmin 关联注册的是 Post)保存到数据库。这个方法接收四个参数,其中前两个,一个是 request,即此次的 HTTP 请求对象,第二个是 obj,即此次创建的关联对象的实例,于是通过复写此方法,就可以将 request.user 关联到创建的 Post 实例上,然后将 Post 数据再保存到数据库:

class PostAdmin(admin.ModelAdmin):
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)

最后还剩下文章的创建时间和修改时间需要填充,一个想法我们可以沿用上面的思路,复写 save_model 方法,将创建的 post 对象关联当前时间,但是这存在一个问题,就是这样做的话只有通过 admin 后台创建的文章才能自动关联这些时间,但创建文章不一定是在 Admin,也可能通过命令行。这时候我们可以通过对 Post 模型的定制来达到目的。

首先,Model 中定义的每个 Field 都接收一个 default 关键字参数,这个参数的含义是,如果将 model 的实例保存到数据库时,对应的 Field 没有设置值,那么 django 会取这个 default 指定的默认值,将其保存到数据库。因此,对于文章创建时间这个字段,初始没有指定值时,默认应该指定为当前时间,所以刚好可以通过 default 关键字参数指定:

from django.utils import timezone

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

这里 default 既可以指定为一个常量值,也可以指定为一个可调用(callable)对象,我们指定 timezone.now 函数,这样如果没有指定 created_time 的值,django 就会将其指定为 timezone.now 函数调用后的值。timezone.now 是 django 提供的工具函数,返回当前时间。因为 timezone 模块中的函数会自动帮我们处理时区,所以我们使用的是 django 为我们提供的 timezone 模块,而不是 Python 提供的 datetime 模块来处理时间。

那么修改时间 modified_time 可以用 default 吗?答案是不能,因为虽然第一次保存数据时,会根据默认值指定为当前时间,但是当模型数据第二次修改时,由于 modified_time 已经有值,即第一次的默认值,那么第二次保存时默认值就不会起作用了,如果我们不修改 modified_time 的值的话,其值永远是第一次保存数据库时的默认值。

所以这里问题的关键是每次保存模型时,都应该修改 modified_time 的值。每一个 Model 都有一个 save 方法,这个方法包含了将 model 数据保存到数据库中的逻辑。通过覆写这个方法,在 model 被 save 到数据库前指定 modified_time 的值为当前时间不就可以了?代码如下:

from django.utils import timezone

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

要注意在指定完 modified_time 的值后,别忘了调用父类的 save 以执行数据保存回数据库的逻辑。

欢迎关注 HelloGitHub 公众号,获取更多开源项目的资料和内容

HelloDjango 第 07 篇:创作后台开启,请开始你的表演!的更多相关文章

  1. HelloDjango 第 11 篇:自动生成文章摘要

    作者:HelloGitHub-追梦人物 文中涉及的示例代码,已同步更新到 HelloGitHub-Team 仓库 博客文章的模型有一个 excerpt 字段,这个字段用于存储文章的摘要.目前为止,还只 ...

  2. 后台大哥请进一步:使用Visual Studio编译scss和souce map实现前后端的完美结合

    title: 后台大哥请进一步:使用Visual Studio编译scss和souce map实现前后端的完美结合 date: 2020-06-28 sidebarDepth: 2 tags: win ...

  3. SpringBoot非官方教程 | 第七篇:springboot开启声明式事务

    转载请标明出处: http://blog.csdn.net/forezp/article/details/70833629 本文出自方志朋的博客 springboot开启事务很简单,只需要一个注解@T ...

  4. HelloDjango 第 09 篇:让博客支持 Markdown 语法和代码高亮

    作者:HelloGitHub-追梦人物 文中涉及的示例代码,已同步更新到 HelloGitHub-Team 仓库 为了让博客文章具有良好的排版,显示更加丰富的格式,我们使用 Markdown 语法来书 ...

  5. Java多线程学习篇——线程的开启

    随着开发项目中业务功能的增加,必然某些功能会涉及到线程以及并发编程的知识点.笔者就在现在的公司接触到了很多软硬件结合和socket通讯的项目了,很多的功能运用到了串口通讯编程,串口通讯编程的安卓端就是 ...

  6. (转)SpringBoot非官方教程 | 第七篇:springboot开启声明式事务

    springboot开启事务很简单,只需要一个注解@Transactional 就可以了.因为在springboot中已经默认对jpa.jdbc.mybatis开启了事事务,引入它们依赖的时候,事物就 ...

  7. HelloDjango 第 08 篇:开发博客文章详情页

    作者:HelloGitHub-追梦人物 文中涉及的示例代码,已同步更新到 HelloGitHub-Team 仓库 首页展示的是所有文章的列表,当用户看到感兴趣的文章时,他点击文章的标题或者继续阅读的按 ...

  8. [python][django学习篇][11]后台admin用户登录博客,添加文章---这一章和博客首页设计没有关系

    1 如果没有创建超级管理员账号,先要创建python manage.py createsuperuser 2 在admin后台注册模型(如果没有这一步,登录http://127.0.0.1:8000/ ...

  9. swift开发网络篇—利用NSURLConnection GET请求和POST请求

    一.GET请求和POST请求简单说明 @IBOutlet weakvar userName:UITextField! @IBOutletweakvar userPwd:UITextField! @IB ...

随机推荐

  1. Java学习笔记之---比较接口与抽象类

    Java学习笔记之---比较接口与抽象类 抽象类是描述事物的本质,接口是描述事物的功能 接口与抽象类的异同 1.一个类只能继承一个父类,但是可以有多个接口 2.抽象类中的抽象方法没有方法体,但是可以有 ...

  2. 基于SpringBoot-Dubbo的微服务快速开发框架

    简介: 基于Dubbo的分布式/微服务基础框架,为前端提供脚手架开发服务,结合前一篇--Web AP快速开发基础框架,可快速上手基于Dubbo的分布式服务开发,项目代码: https://github ...

  3. git分支创建与切换

    1. 场景描述 新版本迭代上线完成,为了保持当前版本稳定性及可回退等需求,需要切换新的分支用于下一版本的迭代开发. 2. 解决方案 2.1 切换前工作. 因发布上线当天有可能存在临时更改文件而未上传g ...

  4. [NOIP2009]靶形数独 题解

    407. [NOIP2009] 靶形数独 时间限制:5 s   内存限制:128 MB [问题描述] 小城和小华都是热爱数学的好学生,最近,他们不约而同地迷上了数独游戏,好胜的他们想用数独来一比高低. ...

  5. 手动部署EJB于WebLogic

    转载自http://blog.sina.com.cn/s/blog_678530f60100hy6c.html 说是转载,其实是我个人几年前在新浪博客上发表的一篇文章 上一篇说道如何使用Eclipse ...

  6. 20131201-插件-XML-第十二天(未完)

    以后再写代码的时候,先从中间层|接口|协议开始入手. 在写XML时注意的事情: 在EditPlus中,Tab是缩进 在头文件中的编码格式是"utf-8"是,在Editplus中保存 ...

  7. CAD2014学习笔记-图纸布局和打印输出

    基于 虎课网huke88.com CAD教程 图纸设计规范:施工图 封面设计:地点.名称.设计人 目录设计:施工图编号.名称.意义.对应页数.注释.图号序号:包括平面.立面.大样图.施工图 设计说明/ ...

  8. ng-bootstrap 组件集中 tabset 组件的实现分析

    ng-bootstrap: tabset 本文介绍了 ng-bootstrap 项目中,tabset 的实现分析. 使用方式 <ngb-tabset> 作为容器元素,其中的每个页签以一个 ...

  9. 【题解】旅行-C++

    Description 某趟列车的最大载客容量为V人,沿途共有n个停靠站,其中始发站为第1站,终点站为第n站.在第1站至第n-1站之 间,共有m个团队申请购票搭乘,若规定:(1)对于某个团队的购票申请 ...

  10. 单元测试jest部署

    引入jest需安装的基础插件: 基础插件 @babel/core 编译工具核心模块包 @babel/preset-env 编译工具,支持es2015特性的编译打包工具包 babel-jest 对.js ...