DjangoAdmin
DjangoAdmin本身就是一套大而全的系统,官方文档中介绍了很多配置方法,但仍然有大量的骚操作是文档中没有的,所以遇到特殊需求的时候,求助文档不一定有用。
在我看来 DjangoAdmin 虽然能快速生成一套管理后台,但如果要做大量特殊需求的定制,其成本不亚于用 Vue/React 重新开发一套,简单的使用成本不高,但深入定制的话需要对 DjangoAdmin 的工作流程比较熟悉,把源码啃熟了(有些 Python 源码没有类型注解是很难读懂的),才能在原有基础上雕花,有时候还存在着后续维护的问题。
不过还是瑕不掩瑜了,谁能拒绝配置了几行代码就可以用的管理后台呢?
而且还不需要你做出一套 RESTFul API 来实现前后端分离,直接一把梭,起飞~
一些参考资料
Django Admin 后台自定制技巧:https://www.cnblogs.com/LyShark/p/12119539.html
DjangoAdmin-字段自动补全(django_admin_autocomplete_all 库)使用与坑:https://django-admin-autocomplete-all.readthedocs.io/en/latest/readme.html
DjangoAdmin添加自定义Widget:http://garmoncheg.blogspot.com/2014/07/django-adding-custom-widget-to-django.html
How to Turn Django Admin Into a Lightweight Dashboard:https://hakibenita.com/how-to-turn-django-admin-into-a-lightweight-dashboard
GitHub 上有很多 DjangoAdmin 的替换主题,所以不要抱怨 DjangoAdmin 的界面丑啦,好看的主题很多!
我最先使用的是 adminx,但这个侵入性太强了,需要对 admin 的配置代码做大量修改,实在是不划算,可能官方也意识到这个问题,后续应该是停更了。
到了 Django 2.x 时代后面,admin 的界面主题多了起来,有个国产的 SimpleUI 很不错,基于 Vue + ElementUI 实现的,star很多,算是比较成熟的一类,我愿称之为国产之光。
其他的我也大多有测试,但用起来总有一些兼容的问题,所以目前还是比较推荐国产之光。
SimpleUI
已经在多个产品中使用,使用 vue+elementUI(非单页应用),支持多标签页
一些相关的参考资料
Django Admin SimpleUI 自定义列:https://simpleui.72wo.com/topic/1266
django-jazzmin
地址:https://github.com/farridav/django-jazzmin
这个是偶然发现的,对于看腻了 ElementUI 的人来说,会有眼前一亮的感觉
使用 Bootstrap+AdminLTE 重写,效果还不错
(就是偶尔会莫名卡死
而且细节方面做得不如 SimpleUI,比如搜索框没有 placeholder 的提示之类的。
本文只记录特殊需求的实现,对于 DjangoAdmin 的常规配置就不复制粘贴了
,网上随便一搜都有很多。
我之前已经写过不少 DjangoAdmin 的定制案例文章了,最近也做了不少定制,不过我不想写新文章来单独记录某个需求的实现过程了,直接在本文里记录,同时保持本文更新~
给Django Admin添加验证码和多次登录尝试限制
Django中间件之实现Admin后台IP白名单
给Django的Admin添加自定义Action 并移除需要选择对象的限制
告别单调,Django后台主页改造 - 使用AdminLTE组件
添加自定义列
本案例基于 SimpleUI
根据不同的发票类型,显示不同颜色的 Tag 组件
这里使用的是 ElementUI 的 Tag 组件,文档:https://element.eleme.cn/#/zh-CN/component/tag
前面提到过 SimpleUI 不是单页应用,是直接在网页上使用 vue 和 elementUI,并没有webpack环境
所以要加入 elementUI 的组件不能直接简单的
<el-tag type="success">标签</el-tag>
而是要用 webpack 生成出来的
<div class="el-tag el-tag--success el-tag--light">标签</div>
ok,开始上Python代码
假设有个 model 叫 Invoice
,中文名发票,定义如下
class Invoice(models.Model):
invoice_type = models.CharField('发票类型')
在需要自定义的 ModelAdmin 中,增加一个方法
# 发票类型颜色
@admin.display(description='发票类别')
def invoice_type_tag(self, obj: Invoice):
def el_tag(color_type, content):
生成 ElementUI 的 tag 组件
:param color_type: success, info, warning, danger
:param content:
:return:
from django.utils.safestring import mark_safe
type_class = '' if len(color_type) == 0 else f'el-tag--{color_type}'
return mark_safe(f'<div class="el-tag el-tag--small {type_class} el-tag--light">{content}</div>')
if obj.invoice_type.startswith('普通'):
return el_tag('', obj.invoice_type)
if obj.invoice_type.startswith('专用'):
return el_tag('danger', obj.invoice_type)
if obj.invoice_type.startswith('电子专票'):
return el_tag('info', obj.invoice_type)
if obj.invoice_type.startswith('电子普票'):
return el_tag('warning', obj.invoice_type)
然后把这个 invoice_type_tag
加到 list_display
中即可
PS:这里的 @admin.display()
装饰器是Django3.2版本之后新增的,很方便,相当于以前的
invoice_type_tag.short_description = '发票类别'
PS:注意HTML代码需要用 mark_safe
方法包装起来,才能正常渲染,不然会被转义!
扩展:添加链接
如果要做成跳转链接,也很容易。
这里用到 ElementUI 的 Link 组件。
@admin.display(description='打开链接')
def open_thumb(self, obj: Photo):
url = reverse('invoice:detail', kwargs={'pk': obj.pk})
return mark_safe(f'''
<a class="el-link el-link--primary is-underline" href="{url}" target="_blank">
<span class="el-link--inner">打开链接</span>
显示进度条
原理同上面的添加自定义列
# 进度条
@admin.display(description='进度条')
def progress_bar(self, obj):
html = f'''
<div role="progressbar" aria-valuenow="{obj.progress}" aria-valuemin="0" aria-valuemax="100"
class="el-progress el-progress--line is-light el-progress--text-inside">
<div class="el-progress-bar">
<div class="el-progress-bar__outer" style="height: 22px;">
<div class="el-progress-bar__inner" style="width: {obj.progress}%;">
<div class="el-progress-bar__innerText">{obj.progress}%</div>
from django.utils.safestring import mark_safe
return mark_safe(html)
页面上显示合计数额
这个功能比较麻烦,因为需要魔改 template
首先我们要知道,这个列表对应的是哪个 template,在 admin 包的 templates 目录下面的找了半天,最终发现这个页面是 change_list
,而且因为页面比较复杂,被分成了好几部分
我们只需要修改 change_list.html
这个文件就行了。
admin.py
OK,模板代码先不管,我们来写Python代码计算总金额。
要实现将数据放在 context
里传给 template,得重写个 ChangeList
对象
from django.db.models import Sum
from django.contrib.admin.views.main import ChangeList
class InvoiceChangeList(ChangeList):
def get_results(self, request):
super(InvoiceChangeList, self).get_results(request)
totals = self.result_list.aggregate(Sum('amount'))
self.total_amount = totals['amount__sum']
使用 Sum
这个聚合方法,计算总金额。
通过Python语言的动态特性,加 total_amount
这个属性添加到 ChangeList
对象中
这样在 template 里就能通过 {{ cl.total_amount }}
的方式拿到这个属性。
然后改一下 ModelAdmin :
class InvoiceAdmin(ImportExportModelAdmin):
# 如果你改了 template 的名称,这里可以对应修改,否则默认即可
change_list_template = 'change_list.html'
# 添加这个代码
def get_changelist(self, request, **kwargs):
return InvoiceChangeList
后端部分搞定了,接下来是前端的模板部分。
template
为了在页面上添加新元素,我们来修改 change_list.html
文件。
**注意,不要直接复制这个文件来修改!**原因是你修改完的 template 会覆盖其他组件,这样以后换 admin 主题,或者使用 import-export 这类会修改 admin 页面的插件时无法生效,也就是所谓的兼容问题。
Django也想到了这种情况,这些 template 都是组件化的,我们写一个扩展 template 就可以了。
在项目的 templates/admin
目录下新建 change_list.html
文件,代码如下
{% extends "admin/change_list.html" %}
{% block result_list %}
{{ block.super }}
<div style="text-align: right; margin: 20px 5px; font-size: 20px;">
总金额:{{ cl.total_amount }} 元
{% endblock %}
注意:如果用了 django-import-export 插件,则要根据使用到的功能来添加 object-tools-items
block。
比如你的 ModelAdmin 继承自 ImportExportModelAdmin
,那我们转到源码,可以看到它重写了 template
class ImportExportMixin(ImportMixin, ExportMixin):
Import and export mixin.
#: template for change_list view
change_list_template = 'admin/import_export/change_list_import_export.html'
class ImportExportModelAdmin(ImportExportMixin, admin.ModelAdmin):
Subclass of ModelAdmin with import/export functionality.
然后再看看 admin/import_export/change_list_import_export.html
这个文件
{% extends "admin/import_export/change_list.html" %}
{% block object-tools-items %}
{% include "admin/import_export/change_list_import_item.html" %}
{% include "admin/import_export/change_list_export_item.html" %}
{{ block.super }}
{% endblock %}
可以看到它在 object-tools-items
中添加了俩组件,把这一块 block
的代码复制到我们的 change_list.html
中即可。
https://stackoverflow.com/questions/34924886/django-admin-changelist-view
分权限的软删除
这个需求是:普通用户删除执行软删除,管理员可以直接删除
在模型里加一个是否删除的字段来表示软删除状态
models.py
class SoftDelete(models.Model):
name = models.CharField('名称', max_length=20)
is_deleted = models.BooleanField('是否软删除', default=False)
def __str__(self):
return self.name
class Meta:
verbose_name = '软删除'
verbose_name_plural = verbose_name
admin.py
@admin.register(SoftDelete)
class SoftDeleteAdmin(admin.ModelAdmin):
list_display = ['name']
fields = ['name']
def get_queryset(self, request: HttpRequest):
# 管理员可以看到全部记录
if request.user.is_superuser:
queryset = super().get_queryset(request)
# 普通用户不能看到被软删除的记录
else:
queryset = super().get_queryset(request).filter(is_deleted=False)
if not self.has_view_or_change_permission(request):
queryset = queryset.none()
return queryset
def get_list_display(self, request: HttpRequest):
# 管理员才能查看是否软删除的状态
if request.user.is_superuser:
insert_if_not_exists(self.list_display, len(self.list_display), 'is_deleted')
else:
# 删掉不给普通用户看的字段
delete_if_exists(self.list_display, 'is_deleted')
return self.list_display
def get_fields(self, request, obj=None):
# 管理员才能查看是否软删除的状态
if request.user.is_superuser:
insert_if_not_exists(self.fields, len(self.fields), 'is_deleted')
else:
# 删掉不给普通用户看的字段
delete_if_exists(self.fields, 'is_deleted')
return self.fields
# 在列表删除
def delete_queryset(self, request: HttpRequest, queryset):
# 管理员才能真正的删除
if request.user.is_superuser:
return queryset.delete()
# 普通用户使用软删除
else:
return queryset.update(is_deleted=True, deleted_user=request.user)
# 在详情页面删除
def delete_model(self, request, obj: SoftDelete):
print('delete_model', obj)
# 管理员才能真正的删除
if request.user.is_superuser:
obj.delete()
# 普通用户使用软删除
else:
obj.is_deleted = True
obj.save()
这里记得处理删除操作要把 delete_queryset
和 delete_model
这俩方法都重写。
delete_queryset
:列表页的删除操作
delete_model
:详情页的删除操作
这部分记录我在逛GitHub时发现的比较有意思的扩展库,记录一下
Django AdminPlus
地址:https://github.com/jsocol/django-adminplus
可以方便的给admin增加新页面
django-adminactions
地址:https://github.com/saxix/django-adminactions
可以给admin添加一系列的actions
Export as CSV
Export as Excel
Export as fixture
Export delete tree
Mass update records
Graph queryset
Merge records
Find Duplicates
If articles including programming codes (e.g. Java, Python, C#, Go) are exceptions, which are released under
所有內容皆以 知识共享署名-相同方式共享 4.0 国际许可协议 进行发布。
含有程序代码內容的文章 (如 Java, Python, C#, Go) 除外,包含的程序代码皆以 GPL v3 发布。
本站使用 StarBlog 开源博客系统,
基于 .NetCore 技术构建