侧边栏壁纸
博主头像
张种恩的技术小栈博主等级

行动起来,活在当下

  • 累计撰写 748 篇文章
  • 累计创建 65 个标签
  • 累计收到 39 条评论

目 录CONTENT

文章目录

Django(8)之Admin后台管理系统

zze
zze
2020-09-27 / 0 评论 / 0 点赞 / 868 阅读 / 21337 字

不定期更新相关视频,抖音点击左上角加号后扫一扫右方侧边栏二维码关注我~正在更新《Shell其实很简单》系列

Admin 后台系统也称为网站后台管理系统,主要用于对网站前台的信息进行管理,如文字、图片、影音和其他日常使用文件的发布、更新、删除等操作,也包括功能信息的统计和管理,如用户信息、订单信息和访客信息等。简单来说,就是对网站数据库和文件的快速操作和管理系统,以使网页内容能够及时得到更新和调整。

走进 Admin

当一个网站上线之后,网站管理员是通过网站后台系统对网站进行管理和维护的。Django 已内置了强大的 Admin 后台系统,在创建 Django 项的时候,可以从配置文件 settings.py 中看到项目已默认启用 Admin 后台系统,如下图所示。

image.png

从图中看到,在 INSTALLED_APPS 中已配置了 Django 的 Admin 后台系统,如果网站不需要 Admin 后台系统,可以将配置信息删除,这样可以减少程序对系统资源的占用。此外,在根目录的 urls.py 中也可以看到 Admin 的 URL 地址信息,我们在浏览器上输入 http://127.0.0.1:8000/admin 就能访问 Admin 后台系统,如下图所示。

image.png

在访问 Admin 后台系统时,首先需要输入用户的账号和密码登录才能进入后台管理界面。创建用户的账号和密码之前,必须确保项目的模型在数据库中有相应的数据表,以 test01 为例,项目的数据表下如图所示。

mysql> show tables;
+----------------------------+
| Tables_in_test01_db        |
+----------------------------+
| auth_group                 |
| auth_group_permissions     |
| auth_permission            |
| auth_user                  |
| auth_user_groups           |
| auth_user_user_permissions |
| django_admin_log           |
| django_content_type        |
| django_migrations          |
| django_session             |
| index_product              |
| index_type                 |
+----------------------------+
12 rows in set (0.00 sec)

如果 Admin 后台系统以英文的形式显示,那么我们还需要在项目的 settings.py 中设置 MIDDLEWARE 中间件,将后台内容以中文形式显示。添加的中间件是有先后顺序的,具体可回顾中间件,设置如下所示。

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

完成上述设置后,下一步是创建用户的账号和密码,创建方法由 Django 的管理工具 manage.py 完成,在 PyCharm 的 Terminal 模式下输入创建指令,如下:

(django_learing) D:\dev\pycharm\django_learning\test01>python manage.py createsuperuser
Username (leave blank to use 'zze'): zze
Email address: 632404164@qq.com
Password: 1234@1234
Password (again): 1234@1234
Superuser created successfully.

在创建用户信息时,用户名和邮箱地址可以为空,如果用户名为空会默认使用计算机的用户名,而设置用户密码时,输入的密码不会显示在计算机的屏幕上。完成用户创建后,打开数据表 auth_user 可看到新增了一条用户信息,如下所示。

mysql> select id, username, email, is_superuser, is_staff, is_active, date_joined from auth_user;
+----+----------+------------------+--------------+----------+-----------+----------------------------+
| id | username | email            | is_superuser | is_staff | is_active | date_joined                |
+----+----------+------------------+--------------+----------+-----------+----------------------------+
|  1 | zze      | 632404164@qq.com |            1 |        1 |         1 | 2020-09-23 07:22:15.971041 |
+----+----------+------------------+--------------+----------+-----------+----------------------------+
1 row in set (0.00 sec)

在 Admin 登录页面上使用刚创建的账号和密码登录,即可进入 Admin 后台系统的页面,如图所示。

image.png

在 Admin 后台系统中可以看到,主要功能分为站点管理、认证和授权、用户和组,说明如下:

  1. 站点管理是整个网站的 App 管理界面,主要管理 Django 的 App 下所定义的模型。
  2. 认证和授权是 Django 内置的认证系统,也是项目的一个 App。
  3. 用户和组是认证和授权所定义的模型,分别对应数据表 auth_userauth_user_groups

test01 中,已在 index 中定义了模型 ProductType,分别对应数据表 index_productindex_type。若想将 index 定义的模型展示在 Admin 后台系统中,则需要在 indexadmin.py 中添加以下代码:

from django.contrib import admin
from .models import *

# 方法一
# 将模型直接注册到 admin 后台
admin.site.register(Product)


# 方法二
# 自定义 ProductAdmin 类并继承 ModelAdmin
# 注册方法一,使用 Python 装饰器将 ProductAdmin 和模型 Product 绑定并注册到后台
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    # 设置显示的字段
    list_display = ['id', 'name', 'weight', 'size', 'type']

# 注册方法二
# admin.site.register(Product,ProductAdmin)

上述代码以两种方法将数据表 index_product 注册到 Admin 后台系统,方法一是基本的注册方式;方法二是通过类的继承方式实现注册。日常的开发都是采用第二种方法实现的,实现过程如下:

  • 自定义 ProductAdmin 类,使其继承 ModelAdminModelAdmin 主要设置模型信息如何展现在 Admin 后台系统中。
  • ProductAdmin 类注册到 Admin 后台系统中有两种方法,两者都是将模型 ProductProductAdmin 类绑定并注册到 Admin 后台系统。

ProductAdmin 类为例,刷新 Admin 后台系统页面,看到站点管理出现 INDEX,代表项目的 indexINDEX 下的 Productsindex 中的模型 Product,对应数
据表 index_product,如下图所示。

image.png

Admin 的基本设置

在上节中,我们将数据表 index_product 成功展现在站点管理的页面,但对一个不会网站开发的使用者来说,可能无法理解 INDEXProducts 的含义,而且用英文表示也会影响整个网页的美观。因此,我们需要将 INDEXProducts 转换成具体的中文内容。将 INDEXProducts 设置中文显示需要分别使用不同的方法实现,因为 INDEXProducts 在项目中分别代表不同的意思,前者是一个 App 的命名,后者是一个 App 中定义的模型。

首先实现 INDEX 的中文显示,主要由 App 的 __init__.py 文件实现,实现代码如下:

# INDEX 设置中文,代码编写在 App(index) 的 __init__.py 文件中
from django.apps import AppConfig
import os

# 修改 App 在 Admin 后台显示的名称
# default_app_config 的值来自 app.py 的类名
default_app_config = 'index.IndexConfig'


# 获取当前 App 的命名
def get_current_app_name(_file):
    return os.path.split(os.path.dirname(_file))[-1]


# 重写类 IndexConfig
class IndexConfig(AppConfig):
    name = get_current_app_name(__file__)
    verbose_name = '网站首页'

当项目启动时,程序会从初始化文件 __init__获取重写的 IndexConfig类,类属性 verbose_name 用于设置 INDEX 的中文内容。
然后将 Products 设置中文显示,在 models.py 中设置类 Meta 的类属性 verbose_name_plural 即可实现。值得注意的是,Meta 的类属性还有 verbose_name,两者都能设置 Products 的中文内容,但 verbose_name 是以复数形式表示的,如将 Products 设置为 '产品信息'verbose_name 会显示为 '产品信息',实现代码如下:

# Products 设置中文,代码编写在 models.py 文件中
# 产品信息表
# 设置字段中文名,用于 Admin 后台显示
class Product(models.Model):
    id = models.AutoField('序号', primary_key=True)
    name = models.CharField('名称', max_length=50)
    weight = models.CharField('重量', max_length=20)
    size = models.CharField('尺寸', max_length=20)
    type = models.ForeignKey(Type, on_delete=models.CASCADE, verbose_name='产品类型')

    # 设置返回值
    def __str__(self):
        return self.name

    class Meta:
        # 如只设置 verbose_name,在 Admin 中会显示为 "产品信息s"
        verbose_name = '产品信息'
        verbose_name_plural = '产品信息'

除此之外,我们还可以进一步完善 Admin 网页标题信息,在 App 的 admin.py 文件中编写以下代码:

# index 的 admin.py 文件
from django.contrib import admin
from .models import *

# 修改 title 和 header
admin.site.site_title = 'test01 后台管理'
admin.site.site_header = 'test01'


@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    # 设置显示的字段
    list_display = ['id', 'name', 'weight', 'size', 'type']

上述例子实现了 INDEXProducts 的中文设置和 Admin 网页标题内容的修改,分别由 index 的初始化文件 __init__、模型文件 models.pyadmin.py 实现。运行
test01 项目,Admin 管理页面如图所示。

image.png

当单击图中的 产品信息 时,网页会进入模型 Product 的数据页面,数据以表格的形式展示。从表格上可以发现,表头信息代表模型的字段,并且表头是以中文的形式展现的。如果细心观察就会发现,在上述例子中,models.py 所定义的模型 Product 的字段设置了中文内容,中文内容是字段参数 verbose_name 的参数值,所以模型 Product 的数据表头才以中文的形式展现,如图所示:

image.png

从图中可以发现,由于产品类型的数据是一个模型 Type 对象,所以在这里并没有成功展示,因此,在模型 Type 中定义 __str__ 函数,设置模型的返回值,代码如下:

# App(index) 的 models.py
# 设置字段中文名,用于 Admin 后台显示
class Type(models.Model):
    id = models.AutoField('序号', primary_key=True)
    type_name = models.CharField('产品类型', max_length=20)

    # 设置返回值,若不设置,则默认返回 Type 对象
    def __str__(self):
        return self.type_name

在浏览器上刷新当前网页,可以发现产品类型的数据变为模型字段 Type_name 的数据,如图所示。

image.png

当一个数据表中存储了成千上万的数据,在 Admin 中查找该表的某条数据信息时,如果不使用一些查找功能,是无法精准地找到需要的数据信息的。为解决这个问题,可以在 admin.py 中进一步优化 ProductAdmin,优化代码如下:

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    # 设置显示的字段
    list_display = ['id', 'name', 'weight', 'size', 'type', ]
    # 设置可搜索的字段并在 Admin 后台数据生成搜索框,如有外键,应使用双下画线连接两个模型的字段
    search_fields = ['id', 'name', 'type__type_name']
    # 设置过滤器,在后台数据的右侧生成导航栏,如有外键,应使用双下画线连接两个模型的字段
    list_filter = ['name', 'type__type_name']
    # 设置排序方式,['id'] 为升序,降序为 ['-id']
    ordering = ['id']
    # 设置时间选择器,如字段中有时间格式才可以使用
    # date_hierarchy = Field
    # 在添加新数据时,设置可添加数据的字段
    fields = ['name', 'weight', 'size', 'type']
    # 设置可读字段,在修改或新增数据时使其无法设置
    readonly_fields = ['name']

上述代码中,ProductAdmin 类分别设置 list_displaysearch_fieldslist_filterorderingdate_hierarchyfieldsreadonly_fields 属性,每个属性的作用在代码注释已有说明。除了 date_hierarchy 之外,其他属性值还可以使用元组格式表示。在 ProductAdmin 类新增的属性都能在页面中生成相应的功能,如图所示。

image.png

image.png

值得注意的是,如果 readonly_fieldsfields 属性设置了模型的同一个字段,那么在新增数据的时候,该模型字段是无法输入数据的。例如上述设置,readonly_fieldsfields 同时设置了 name 字段,在新增数据时,该字段的值默认为空并且无法输入数据,如图所示。

image.png

Admin 的二次开发

前面讲述了 Admin 的基本设置,但实际上每个网站的功能和需求都是各不相同的,这也导致了 Admin 后台功能有所差异。因此,通过重写 ModelAdmin 的方法可以实现 Admin 的二次开发,满足多方面的开发需求。

函数 get_readonly_field

函数 get_readonly_fields 和属性 readonly_fields 的功能相似,不过前者比后者更为强大。比如使用函数 get_readonly_fields 实现不同的用户角色来决定字段的可读属性,实现代码如下:

# 重写 ProductAdmin 的 get_readonly_fields 方法
def get_readonly_fields(self, request, obj=None):
    if request.user.is_superuser:
        self.readonly_fields = []
    return self.readonly_fields

函数 get_readonly_fields 首先判断当前发送请求的用户是否为超级用户,如果符合判断条件,将重新设置 readonly_fields 属性,使当前用户具有全部字段的编辑权限。其中,函数参数 request 是当前用户的请求对象,参数 obj 是模型对象,默认值为 None

在浏览器上通过切换不同的用户登录,可以发现在新增或者修改数据的时候,不同的用户身份对字段 name 的操作权限有所不同,如图所示。

image.png

image.png

设置字段格式

在 Admin 预览模型 Product 的数据信息时,数据表的表头是由属性 list_display 所定义的,每个表头的数据都来自于数据库,并且数据以固定的字体格式显示在网页上。若要对某些字段的数据进行特殊处理,如设置数据的字体颜色,以模型 Producttype 字段为例,将该字段的数据设置为不同的颜色,实现代码如下:

# models.py 的模型 Product
# 产品信息表
class Product(models.Model):
    id = models.AutoField('序号', primary_key=True)
    name = models.CharField('名称', max_length=50)
    weight = models.CharField('重量', max_length=20)
    size = models.CharField('尺寸', max_length=20)
    type = models.ForeignKey(Type, on_delete=models.CASCADE, verbose_name='产品类型')

    # 自定义函数,设置字体颜色
    def colored_type(self):
        if '手机' in self.type.type_name:
            color_code = 'red'
        elif '平板电脑' in self.type.type_name:
            color_code = 'blue'
        elif '智能穿戴' in self.type.type_name:
            color_code = 'green'
        else:
            color_code = 'yellow'
        # from django.utils.html import format_html
        return format_html('<span style="color: {};">{}</span>', color_code, self.type)

    # 自定义字段的 header 名称
    colored_type.short_description = '带颜色的产品类型'

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = '产品信息'
        verbose_name_plural = '产品信息'

然后在 admin.pyProductAdmin 中添加自定义字段 colored_type

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    list_display = ['id', 'name', 'weight', 'size', 'type', 'colored_type']
    ...

从上述代码可以看到,设置字段的数据格式主要由文件 models.pyadmin.py 实现,说明如下:

  • models.py 的模型 Product 中定义函数 colored_type,函数名可以自行命名,该函数通过判断模型字段的数据内容,从而返回不同的字体颜色。
  • 然后在 admin.py 的类 ProductAdmin 的属性 list_display 中添加模型 Product 的函数 colored_type,使该函数以表头的形式显示在 Admin 后台的数据信息页面
    上。运行结果如图所示。

image.png

函数 get_queryset

函数 get_queryset 根据不同用户角色设置数据的访问权限,该函数可以将一些重要的数据进行过滤。以模型 Product 为例,在 admin.py 的类 ProductAdmin 中重写函数 get_queryset,代码如下:

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    ...
    # admin.py 的类 ProductAdmin
    # 根据当前用户名设置数据访问权限
    def get_queryset(self, request):
        qs = super(ProductAdmin, self).get_queryset(request)
        if request.user.is_superuser:
            return qs
        else:
            return qs.filter(id__lt=3)

分析上述代码可知,自定义函数 get_queryset 的代码说明如下:

  • 首先通过 super 方法来获取父类 ModelAdmin 的函数 get_queryset 所生成的查询对象,该对象用于查询模型 Product 的全部数据。
  • 然后判断当前用户的用户身份,如果为超级用户,函数返回模型 Product 的全部数据,否则只返回模型 Product 的前 3 条数据。

运行结果如图所示。

image.png

函数 formfield_for_foreignkey

函数 formfield_for_foreignkey 用于在新增或修改数据的时候,设置外键的可选值。如果在模型中将某字段定义为外键类型,当新增数据时,该字段为一个下拉框控件,下拉框中的数据来自于该字段所指向的模型,如图所示。

image.png

如果想要对下拉框中的数据实现过滤功能,可以对函数 formfield_for_foreignkey 进行重写,代码如下:

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    ...
    # 新增或修改数据时,设置外键可选值
    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == 'type':
            if not request.user.is_superuser:
                kwargs['queryset'] = Type.objects.filter(id__lte=4)
        return super(ProductAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)

上述代码通过重写函数 formfield_for_foreignkey,实现下拉框的数据过滤,实现说明如下:

  • 参数 db_field 是模型 Product 的外键对象,一个模型可以定义多个外键,因此函数首先对外键名进行判断。
  • 然后判断当前用户是否为超级用户,参数 request 是当前用户的请求对象。
  • 如果当前用户为普通用户,则设置参数 kwargsqueryset,参数 kwargs 是以字典的形式作为函数参数,queryset 是参数 kwargs 的键。
  • 最后将设置好的参数 kwargs 传递给父类的函数 formfield_for_foreignkey 重新执行。

运行结果如图所示。

image.png

函数 save_model

函数 save_model 是在新增或修改数据的时候,点击保存按钮所触发的功能,该函数主要对输入的数据进行入库和更新处理。若想在这功能中加入一些特殊的功能需求,可以对函数 save_model 进行重写。比如对数据的修改实现一个日志记录,那么函数 save_model 的实现代码如下:

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    ...
    # 修改保存方法
    def save_model(self, request, obj, form, change):
        if change:
            # 获取当前用户名
            user = request.user
            # 使用模型获取数据,pk 代表具有主键属性的字段
            name = self.model.objects.get(pk=obj.pk).name
            # 使用表单获取数据
            weight = form.cleaned_data['weight']
            # 写入日志文件
            with open('./test01_log.txt', 'a') as f:
                f.write('产品:{},被用户:{},修改\r\n'.format(name, user))
        else:
            pass

        # 使用 super 可使自定义 save_model 既保留父类已有的功能又添加自定义功能
        super(ProductAdmin, self).save_model(request, obj, form, change)

上述代码中,函数 save_model 的功能说明如下:

  • 首先判断参数 change 是否为 True,若为 True,则说明当前操作为数据修改,反之为新增数据。
  • 分别从三个函数参数中获取相关的数据内容。参数 request 代表当前用户的请求对象,参数 obj 代表当前数据所对应的模型对象,参数 form 代表 Admin 的数据修改页面所对应的数据表单。
  • 然后将所获取的数据写入文本文件,实现简单的日志记录功能。
  • 最后使用 super 方法使重写函数 save_model 执行原有函数 save_model 的功能,对数据进行入库和变更处理。若将此代码注释,当触发重写函数时,程序只执行日志记录功能,并不执行数据入库和变更处理。

除此之外,还有数据删除所执行的函数 delete_model,代码如下:

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    ...
    def delete_model(self, request, obj):
        pass
        super(ProductAdmin, self).delete_model(request, obj)

自定义模板

Admin 后台系统的 HTML 模板是由 Django 提供的,我们可以在 Django 的安装目录下找到 Admin 模板所在的路径(\Lib\site-packages\django\contrib\admin\templates\admin)。如果想要对 Admin 的模板进行自定义更改,可直接修改 Django 里面的 Admin 模板,但一般不提倡这种方法。如果一台计算机同时开发多个 Django 项目,这样会影响其他项目的使用。除了这种方法之外,还可以利用模板继承的方法实现自定义模板开发。我们对 test01 项目的目录架构进行调整,如图所示。

image.png
在项目中创建模板文件夹 templates,在 templates 下依次创建文件夹 adminindex,说明如下:

  • 文件夹 admin 代表该文件里的模板用于 Admin 后台管理系统,而且文件夹必须命名为 admin
  • 文件夹 index 代表项目的 App,文件夹的命名必须与 App 的命名一致。该文件夹存放模板文件 change_form.html,并且模板文件只适用于 index 的后台数据。
  • 如果将模板 change_form.html 放在 admin 文件夹下,说明该文件适用于当前项目的所有 App。

值得注意的是,在项目中创建文件夹 templates 时,切勿忘记在项目 settings.py 中配置 templates 的路径信息。最后在模板 change_form.html 中编写以下代码:

{% extends "admin/change_form.html" %}
{% load i18n admin_urls static admin_modify %}
{% block object-tools-items %}
    {# 判断当前用户角色 #}
    {% if request.user.is_superuser %}
        <li>
            <a href="{% add_preserved_filters history_url %}" class="historylink">{% trans "History" %}</a>
        </li>
    {% endif %}
    {% if has_absolute_url %}
        <li><a href="{{ absolute_url }}" class="viewsitelink">{% trans "view on site" %}</a></li>
    {% endif %}
{% endblock %}

从代码可以看到,自定义模板 change_form.html 的代码说明如下:

  • 自定义模板 change_form.html 首先继承自 Admin 源模板 change_form.html,自定义模板的命名必须与源模板的命名一致。
  • 例如源模版 admin/change_form.html 导入了标签 {% load il8n admin_urls static admin_modify %},因此自定义模版 change_form.html 也需要导入该模版标签。
  • 通过使用 block 标签实现源模板的代码重写。我们查看源模板的代码发现,模板代码以 {% block xxx %} 形式分块处理,将网页上不同的功能都一一区分了。因此,在自定义模板中使用 block 标签可对某个功能进行自定义开发。

项目运行时,程序优先查找项目文件夹 admin 的模板文件,若找不到相应的模板文件,再从 Django 中的 admin 源模板查找。在上述例子中,使用超级用户和普通用户分别进入产品信息的数据修改页面时,不同的用户角色所返回的页面会有所差异,如下图所示。

image.png

image.png
除了上述例子之外,Django 的 Admin 后台管理系统还提供了许多功能函数,具体函数的说明以及使用此处不做一一讲解,有兴趣可查阅 Django 官方文档说明。

0

评论区