Dust8 的博客

读书百遍其义自见

0%

有些自定义的后台页面, 但是有权限才能操作. 用的 simpleui 不支持权限, simple pro 才支持, 不是免费的. 只能自己定制.

添加后台自定义导航

这样添加后是所有用户都看得到导航

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 自定义导航
SIMPLEUI_CONFIG = {
'system_keep': True,
'dynamic': True, # 设置是否开启动态菜单, 默认为False. 如果开启, 则会在每次用户登陆时动态展示菜单内容
'menus': [{
'name': '自定义导航',
'icon': 'fa fa-file',
'codename': 'c_app',
'models': [{
'name': '添加同学',
'url': 'c_app/c_xx/',
'icon': 'fas fa-home',
'codename': 'c_xx',
}, ],
}, ],
}

自定义导航支持权限

数据库操作

表名 名字 作用
django_content_type 已经安装模型表 用来描述已经安装的模型
auth_permission 权限表 用户记录所有模块的view,add,change,delete权限,与django_content_type关联

django_content_type 里面添加 app 和 模型

1
INSERT INTO  `django_content_type`(`app_label`,`model`) VALUES ('c_app','c_xx');

插入成功后需要记住 id 的值, 例如 95

auth_permission 表里面添加权限记录. 增删改查每种对应一条权限, 例如查看权限已 view 开头

1
INSERT INTO `auth_permission`(`name`,`content_type_id`,`codename`)VALUES('Can view xx', 95 , 'view_c_xx');

后台给用户分配权限

在后台给用户把权限加上

权限判断

查看了 simpleui 导航代码, 把他修改了下, 注释定制的就是不一样的代码.
app 目录下面新建 templatetags 文件夹里面新建 simpletags.py 文件, 里面定制一个菜单导航 c_menus

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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# simpletags.py
import copy
import json

from simpleui.templatetags.simpletags import get_config, _import_reload, get_icon, unicode_to_str, LazyEncoder, register


@register.simple_tag(takes_context=True)
def c_menus(context, _get_config=None):
data = []

# return request.user.has_perm("%s.%s" % (opts.app_label, codename))
if not _get_config:
_get_config = get_config

config = _get_config('SIMPLEUI_CONFIG')
if not config:
config = {}

if config.get('dynamic', False) is True:
config = _import_reload(_get_config('DJANGO_SETTINGS_MODULE')).SIMPLEUI_CONFIG

app_list = context.get('app_list')
for app in app_list:
_models = [
{
'name': m.get('name'),
'icon': get_icon(m.get('object_name'), unicode_to_str(m.get('name'))),
'url': m.get('admin_url'),
'addUrl': m.get('add_url'),
'breadcrumbs': [{
'name': app.get('name'),
'icon': get_icon(app.get('app_label'), app.get('name'))
}, {
'name': m.get('name'),
'icon': get_icon(m.get('object_name'), unicode_to_str(m.get('name')))
}]
}

for m in app.get('models')
] if app.get('models') else []

module = {
'name': app.get('name'),
'icon': get_icon(app.get('app_label'), app.get('name')),
'models': _models
}
data.append(module)

# 如果有menu 就读取,没有就调用系统的
key = 'system_keep'

# [定制]获取当前用户权限
permissions = context.request.user.get_all_permissions()

if config and 'menus' in config:
if config.get(key, None):
temp = copy.deepcopy(config.get('menus'))
for i in temp:
# 处理面包屑
if 'models' in i:
# [定制]去掉没有权限的自定义菜单
for k in i.get('models')[:]:
if 'codename' in k and f"{i['codename']}.view_{k['codename']}" not in permissions:
if not context.request.user.is_superuser:
i.get('models').remove(k)
continue

k['breadcrumbs'] = [{
'name': i.get('name'),
'icon': i.get('icon')
}, {
'name': k.get('name'),
'icon': k.get('icon')
}]
else:
i['breadcrumbs'] = [{
'name': i.get('name'),
'icon': i.get('icon')
}]

# [定制]去掉空的一级菜单
if 'models' in i and len(i['models']) == 0:
continue

data.append(i)
else:
data = config.get('menus')

# 获取侧边栏排序, 如果设置了就按照设置的内容排序, 留空则表示默认排序以及全部显示
if config.get('menu_display') is not None:
display_data = list()
for _app in data:
if _app['name'] not in config.get('menu_display'):
continue
_app['_weight'] = config.get('menu_display').index(_app['name'])
display_data.append(_app)
display_data.sort(key=lambda x: x['_weight'])
data = display_data

# 给每个菜单增加一个唯一标识,用于tab页判断
eid = 1000
for i in data:
eid += 1
i['eid'] = eid
if 'models' in i:
for k in i.get('models'):
eid += 1
k['eid'] = eid

menus_string = json.dumps(data, cls=LazyEncoder)

# 把data放入session中,其他地方可以调用
if not isinstance(context, dict) and context.request:
context.request.session['_menus'] = menus_string

return '<script type="text/javascript">var menus={}</script>'.format(menus_string)

模板使用定制导航

使用自定义 admin 应用模板, 把 simpleui 的模板文件 index.html 里面的 menus 改成定制的 c_menus

1
2
3
4
5
{% block menus %}
{% autoescape off %}
{% c_menus %}
{% endautoescape %}
{% endblock %}

参考链接

在服务器用端口部署了多个 django 后台, 导致只能登录一个后台, 登录信息被覆盖了.
查了下可以更改配置文件里面的 SESSION_COOKIE_NAME 来区分登录信息.
默认的值是 sessionid, 可以改成不一样的, 这样就区分开来了.
设置好后 打开浏览器开发控制台 -> Application -> Cookies 里面看到是否设置成功.

1
2
# settings.py
SESSION_COOKIE_NAME = "sessionid_xxx"

参考链接

django 使用统计 annotateaggregate 时由于没有数据导致统计是 null , 而不是 0.
这对返回结果产生很大影响, 可以使用 Coalesce 方法来设置默认值.

1
2
3
4
5
6
7
8
9
10
11
from django.db.models import Sum, Value as V
from django.db.models.functions import Coalesce

>>> # Prevent an aggregate Sum() from returning None
>>> aggregated = Author.objects.aggregate(
... combined_age=Coalesce(Sum('age'), V(0)),
... combined_age_default=Sum('age'))
>>> print(aggregated['combined_age'])
0
>>> print(aggregated['combined_age_default'])
None

参考链接

django 升级后 django_admin_log 表里面的 change_message 字段里面的内容变成了中文, 估计是记录字段的 verbose_name 了.类型下面

1
[{"changed": {"fields": ["\u5e10\u53f7\u603b\u989d", "\u53ef\u7528\u4f59\u989d"]}}]

1
SELECT * FROM `django_admin_log` where `change_message` LIKE CONCAT('%','_u5e10_u53f7_u603b_u989d','%')

安装 pt-archiver

percona-toolkit 只支持 linux.

1
2
3
4
5
6
7
8
# 下载安装包
wget https://www.percona.com/downloads/percona-toolkit/3.2.1/binary/debian/bionic/x86_64/percona-toolkit_3.2.1-1.bionic_amd64.deb

# 安装
sudo dpkg -i percona-toolkit_3.2.1-1.bionic_amd64.deb

# 如果使用 --ask-pass 需要安装libterm-readkey-perl
sudo apt install libterm-readkey-perl

归档策略

先迁移数据到归档表, 在删除原本的已迁移数据

迁移全部数据,并替换为更新了的数据

--where '1=1' 表示迁移全表. --no-delete 表示不删除原表. --replace, 有些是关联的外键,必须有, 而且数据又会更新, 所以使用替换. --nosafe-auto-increment 表示迁移条件的所以数据, 不然会保留一条数据, 用来避免 mysql 重启后没有记住最后记录的自增 id, 要慎用. --ask-pass 表示输入密码

1
pt-archiver --source h=192.168.0.122,P=3306,u=root,D=oxbfund,t=users_userprofile,A=utf8 --dest h=192.168.0.122,P=3306,u=root,D=axbfund,t=users_userprofile,A=utf8 --charset=utf8 --where '1=1' --progress 500 --txn-size=1000 --statistics --no-delete --replace  --nosafe-auto-increment --ask-pass

迁移需要归档的老数据

--no-check-charset 是不检查数据库的编码

1
pt-archiver --source h=192.168.0.122,P=3306,u=root,D=oxbfund,t=trade_payrecorder,A=utf8 --dest h=192.168.0.122,P=3306,u=root,D=axbfund,t=trade_payrecorder,A=utf8 --charset=utf8 --where 'classorder_info_id in (SELECT order_info_id FROM trade_orderresell where resell_status="RESELL_FINISHED" and match_time < "2020-07-01")' --progress 500 --txn-size=1000 --statistics --no-delete  --no-check-charset --ask-pass

删除已经迁移的数据

--purge 表示清除

1
pt-archiver --source h=192.168.0.122,P=3306,u=root,D=oxbfund,t=trade_payrecorder,A=utf8 --charset=utf8 --where 'classorder_info_id in (SELECT order_info_id FROM trade_orderresell where resell_status="RESELL_FINISHED" and match_time < "2020-07-01")' --progress 500 --txn-size=1000 --statistics --no-check-charset  --purge --ask-pass

参考链接