向Django Admin添加图表
介绍
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上找到。
你喜欢这个帖子吗?
当我们编写新内容时,接收更新。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)