介绍
Django 提供功能管理 UI 开箱即用的 CRUD 界面用于 db 管理。这包括基本内容和用户管理系统的大多数用例。但是,它没有显示摘要或历史趋势的探索性视图,这是您期望从管理仪表板获得的内容。
幸运的是,django管理应用程序是可扩展的,通过一些调整,我们可以添加交互式Javascript图表到管理员。
问题
我想在findwork.dev上获取电子邮件订阅者随时间的图表概览。就电子邮件订阅者而言,网站是增长还是停滞?上个月我们有多少订户?我们获得大多数订阅者的星期是哪一周?是否所有订阅者都在验证其电子邮件?
使用探索性图表,我们可以获得网站性能的历史概述。
我最初探索了现成的Django管理应用程序和仪表板的土地。要求是,它包括图表能力,有详细的记录,看起来不错。虽然我尝试的所有应用程序看起来都比默认管理员在样式方面更好,但它们要么缺少文档,要么没有维护。
- xadmin - 没有英文文档
- django-jet - 未经维护,因为核心团队正在研究SaaS 替代方案
- django-grapinelli - 无制图能力
这时,一个想法突然浮现在脑海:为什么不扩展默认管理应用呢?
扩展 django 管理员
django管理应用程序是由模型管理类组成。这些表示在管理界面中的模型的可视视图。默认情况下,ModelAdmin 类附带 5 个默认视图:
- 更改列表 - 模型集合的列表视图
- 添加 - 允许您添加新模型实例的视图
- 更改 - 用于更新模型实例的视图
- 删除 - 用于确认删除模型实例的视图
- 历史记录 - 对模型实例执行的操作的历史记录
当您要查看特定模型时,"更改列表"视图是默认管理员视图。我想在这里添加一个图表,以便每当我打开电子邮件订阅者页面时,都会显示随着时间的推移添加的订阅者。
假设我们有一个电子邮件订阅者模型,如下所示:
1
2
3
4
5
6
7
|
# web/models.py
from
django
.
db
import
models
class
EmailSubscriber
(
models
.
Model
)
:
email
=
models
.
EmailField
(
)
created_at
=
models
.
DateTimeField
(
)
|
为了在管理应用中呈现电子邮件订阅者,我们需要创建一个从 扩展的类。django.contrib.admin.ModelAdmin
基本模型管理员如下所示:
1
2
3
4
5
6
7
8
9
|
# web/admin.py
from
django
.
contrib
import
admin
from
.
models
import
EmailSubscriber
@
admin
.
register
(
EmailSubscriber
)
class
EmailSubscriberAdmin
(
admin
.
ModelAdmin
)
:
list_display
=
(
"id"
,
"email"
,
"created_at"
)
# display these table columns in the list view
ordering
=
(
"-created_at"
,
)
# sort by most recent subscriber
|
让我们添加一些订阅者,以便我们有一个初始数据集:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
$
.
/
manage
.
py
shell
Python
3.7.3
(
default
,
Apr
9
2019
,
04
:
56
:
51
)
[
GCC
8.3.0
]
on
linux
Type
"help"
,
"copyright"
,
"credits"
or
"license"
for
more
information
.
(
InteractiveConsole
)
from
web
.
models
import
EmailSubscriber
from
django
.
utils
import
timezone
from
datetime
import
timedelta
import
random
for
i
in
range
(
0
,
100
)
:
EmailSubscriber
.
objects
.
create
(
email
=
f
"user_{i}@email.com"
,
created_at
=
timezone
.
now
(
)
-
timedelta
(
days
=
random
.
randint
(
0
,
100
)
)
)
.
.
.
<
EmailSubscriber
:
EmailSubscriber
object
(
1
)
>
<
EmailSubscriber
:
EmailSubscriber
object
(
2
)
>
<
EmailSubscriber
:
EmailSubscriber
object
(
3
)
>
.
.
.
|
如果我们输入 ChangeList 视图,我们将看到我们添加了 100 个新订阅者,随机创建时间http://localhost:8000/admin/web/emailsubscriber/。
假设我们要添加一个图表,该图表汇总了一段时间内条形图中的订阅者数量。我们希望将其放在订阅者列表的上方,这样,您一进入网站即可见。
下面的红色区域勾勒出我想直观地放置图表的位置。
如果我们创建一个新文件,我们可以强制 django 管理员加载我们的模板,而不是默认模板。让我们在
web/templates/admin/web/emailsubscriber/change_list.html
.
重写管理模板时的命名方案是
{{app}}/templates/admin/{{app}}/{{model}}/change_list.html
.
默认的 ChangeList 视图是可扩展的,并且有多个块可以覆盖以满足您的需要。检查默认管理模板时,我们可以看到它包含可以重写的块。我们需要重写内容块,以更改模型表之前呈现的内容。
让我们扩展默认的"更改列表"视图并添加自定义文本:
1
2
3
4
5
6
7
8
9
10
11
12
|
# web/templates/admin/web/emailsubscriber/change_list.html
{
%
extends
"admin/change_list.html"
%
}
{
%
load
static
%
}
{
%
block
content
%
}
<
h1
>
Custom
message
!
<
/
h1
>
<
!
--
Render
the
rest
of
the
ChangeList
view
by
calling
block
.
super
--
>
{
{
block
.
super
}
}
{
%
endblock
%
}
|
酷,我们现在已经设法自定义管理用户界面。让我们更进一步,使用Chart.js添加 Javascript 图表。我们需要重写外头块来添加脚本和样式元素来在标头中加载 Chart.js。
Chart.js代码基于此处找到的演示条形图。我稍微修改了它,以读取 X 轴上的时间序列数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
# web/templates/admin/web/emailsubscriber/change_list.html
{
%
extends
"admin/change_list.html"
%
}
{
%
load
static
%
}
<
!
--
Override
extrahead
to
add
Chart
.
js
--
>
{
%
block
extrahead
%
}
{
{
block
.
super
}
}
<
link
rel
=
"stylesheet"
href
=
"https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.css"
/
>
<script
src
=
"https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.bundle.min.js"
>
</script>
<script>
document
.
addEventListener
(
'DOMContentLoaded'
,
(
)
=
>
{
const
ctx
=
document
.
getElementById
(
'myChart'
)
.
getContext
(
'2d'
)
;
// Sample data
const
chartData
=
[
{
"date"
:
"2019-08-08T00:00:00Z"
,
"y"
:
3
}
,
{
"date"
:
"2019-08-07T00:00:00Z"
,
"y"
:
10
}
,
{
"date"
:
"2019-08-06T00:00:00Z"
,
"y"
:
15
}
,
{
"date"
:
"2019-08-05T00:00:00Z"
,
"y"
:
4
}
,
{
"date"
:
"2019-08-03T00:00:00Z"
,
"y"
:
2
}
,
{
"date"
:
"2019-08-04T00:00:00Z"
,
"y"
:
11
}
,
{
"date"
:
"2019-08-02T00:00:00Z"
,
"y"
:
3
}
,
{
"date"
:
"2019-08-01T00:00:00Z"
,
"y"
:
2
}
,
]
;
// Parse the dates to JS
chartData
.
forEach
(
(
d
)
=
>
{
d
.
x
=
new
Date
(
d
.
date
)
;
}
)
;
// Render the chart
const
chart
=
new
Chart
(
ctx
,
{
type
:
'bar'
,
data
:
{
datasets
:
[
{
label
:
'new subscribers'
,
data
:
chartData
,
backgroundColor
:
'rgba(220,20,20,0.5)'
,
}
,
]
,
}
,
options
:
{
responsive
:
true
,
scales
:
{
xAxes
:
[
{
type
:
'time'
,
time
:
{
unit
:
'day'
,
round
:
'day'
,
displayFormats
:
{
day
:
'MMM D'
,
}
,
}
,
}
,
]
,
yAxes
:
[
{
ticks
:
{
beginAtZero
:
true
,
}
,
}
,
]
,
}
,
}
,
}
)
;
}
)
;
</script>
{
%
endblock
%
}
{
%
block
content
%
}
<
!
--
Render
our
chart
--
>
<
div
style
=
"width: 80%;"
>
<
canvas
style
=
"margin-bottom: 30px; width: 60%; height: 50%;"
id
=
"myChart"
>
<
/
canvas
>
<
/
div
>
<
!
--
Render
the
rest
of
the
ChangeList
view
--
>
{
{
block
.
super
}
}
{
%
endblock
%
}
|
Voil®,我们现在已经呈现了一个图表.js图表到django管理员。唯一的问题是数据是硬编码的,而不是从我们的后端派生的。
将图表数据注入管理模板
ModelAdmin 类具有一个称为更改列表_视图的方法。此方法负责呈现 ChangeList 页。通过重写此方法,我们可以将图表数据注入到模板上下文中。
下面的代码大致执行以下操作:
- 以每日间隔聚合新订户的总数
- 将 Django 查询集编码为 JSON
- 将数据添加到模板上下文
- 调用 super() 方法来呈现页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
# django_admin_chart_js/web/admin.py
import
json
from
django
.
contrib
import
admin
from
django
.
core
.
serializers
.
json
import
DjangoJSONEncoder
from
django
.
db
.
models
import
Count
from
django
.
db
.
models
.
functions
import
TruncDay
from
.
models
import
EmailSubscriber
@
admin
.
register
(
EmailSubscriber
)
class
EmailSubscriberAdmin
(
admin
.
ModelAdmin
)
:
list_display
=
(
"id"
,
"email"
,
"created_at"
)
ordering
=
(
"-created_at"
,
)
def
changelist_view
(
self
,
request
,
extra_context
=
None
)
:
# Aggregate new subscribers per day
chart_data
=
(
EmailSubscriber
.
objects
.
annotate
(
date
=
TruncDay
(
"created_at"
)
)
.
values
(
"date"
)
.
annotate
(
y
=
Count
(
"id"
)
)
.
order_by
(
"-date"
)
)
# Serialize and attach the chart data to the template context
as_json
=
json
.
dumps
(
list
(
chart_data
)
,
cls
=
DjangoJSONEncoder
)
extra_context
=
extra_context
or
{
"chart_data"
:
as_json
}
# Call the superclass changelist_view to render the page
return
super
(
)
.
changelist_view
(
request
,
extra_context
=
extra_context
)
|
数据现在应在技术上添加到模板上下文中,但现在我们必须在图表中使用它,而不是硬编码数据。
将图表数据变量中的硬编码数据替换为后端的数据:
1
2
3
|
/
/
django_admin_chart_js
/
web
/
templates
/
admin
/
web
/
emailsubscriber
/
change_list
.
html
const
chartData
=
{
{
chart_data
|
safe
}
}
;
|
重新加载页面以查看我们美丽的图表。
使用 JS 动态加载数据
在上面的示例中,我们将初始图表数据直接注入 html 模板。在初始页面加载后,我们可以进行更多的交互式和提取数据。为此,我们需要:
- 向模型管理员添加新终结点,该终结点返回 JSON 数据
- 添加 JS 逻辑,在按钮单击时进行 AJAX 调用并重新呈现图表
添加新终结点需要我们将get_urls()方法覆盖到模型管理员之上,并注入我们自己的终结点 URL。
请务必注意,您的自定义 URL 应先于默认 URL。默认的允许性,将匹配任何内容,因此请求永远不会通过我们的自定义方法。
我们的 python 代码现在应该如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
# web/admin.py
import
json
from
django
.
contrib
import
admin
from
django
.
core
.
serializers
.
json
import
DjangoJSONEncoder
from
django
.
db
.
models
import
Count
from
django
.
db
.
models
.
functions
import
TruncDay
from
django
.
http
import
JsonResponse
from
django
.
urls
import
path
from
.
models
import
EmailSubscriber
@
admin
.
register
(
EmailSubscriber
)
class
EmailSubscriberAdmin
(
admin
.
ModelAdmin
)
:
list_display
=
(
"id"
,
"email"
,
"created_at"
)
ordering
=
(
"-created_at"
,
)
.
.
.
def
get_urls
(
self
)
:
urls
=
super
(
)
.
get_urls
(
)
extra_urls
=
[
path
(
"chart_data/"
,
self
.
admin_site
.
admin_view
(
self
.
chart_data_endpoint
)
)
]
# NOTE! Our custom urls have to go before the default urls, because they
# default ones match anything.
return
extra_urls
+
urls
# JSON endpoint for generating chart data that is used for dynamic loading
# via JS.
def
chart_data_endpoint
(
self
,
request
)
:
chart_data
=
self
.
chart_data
(
)
return
JsonResponse
(
list
(
chart_data
)
,
safe
=
False
)
def
chart_data
(
self
)
:
return
(
EmailSubscriber
.
objects
.
annotate
(
date
=
TruncDay
(
"created_at"
)
)
.
values
(
"date"
)
.
annotate
(
y
=
Count
(
"id"
)
)
.
order_by
(
"-date"
)
)
|
我们还需要添加 Javascript 逻辑,以在按钮单击时重新加载图表数据并重新呈现图表。在图表变量的声明下方添加以下行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
/
/
django_admin_chart_js
/
web
/
templates
/
admin
/
web
/
emailsubscriber
/
change_list
.
html
const
chart
=
new
Chart
.
.
.
.
.
.
/
/
Reload
chart
data
from
the
backend
on
button
click
const
btn
=
document
.
querySelector
(
'#reload'
)
;
btn
.
addEventListener
(
'click'
,
async
(
)
=
>
{
const
res
=
await
fetch
(
"/admin/web/emailsubscriber/chart_data/"
)
;
const
json
=
await
res
.
json
(
)
;
json
.
forEach
(
(
d
)
=
>
{
d
.
x
=
new
Date
(
d
.
date
)
;
}
)
;
chart
.
data
.
datasets
[
0
]
.
data
=
json
;
chart
.
update
(
)
;
}
)
;
|
在图表中添加下面的 html 按钮:
1
2
3
4
5
6
7
8
9
10
11
|
{
%
block
content
%
}
<
!
--
Render
our
chart
--
>
<
div
style
=
"width: 80%;"
>
<
canvas
style
=
"margin-bottom: 30px; width: 60%; height: 50%;"
id
=
"myChart"
>
<
/
canvas
>
<
/
div
>
<
button
id
=
"reload"
style
=
"margin: 1rem 0"
>
Reload
chart
data
<
/
button
>
<
!
--
Render
the
rest
of
the
ChangeList
view
--
>
{
{
block
.
super
}
}
{
%
endblock
%
}
|
Chart.js 附带不同的开箱式可视化效果。它很容易得到的基本图表,并提供定制,以万一你需要它。他们的文件在这里。Django 管理文档在这里
完整的示例代码可以在Github上找到。
你喜欢这个帖子吗?
当我们编写新内容时,接收更新。