Dust8 的博客

读书百遍其义自见

0%

原理

django admin 后台管理的菜单层级是 app -> model, 所以如果要加顶级菜单就创建一个应用, 要加二级菜单就创建一个模型.

示例

顶级菜单

创建新应用z_app

1
python manage.py startapp z_app

修改app显示信息

1
2
3
4
# apps.py
class ZAppConfig(AppConfig):
...
verbose_name="自定义菜单"

在把新的app放入 INSTALLED_APPS, 这样就会在后台显示了

1
2
3
4
5
# settings.py
INSTALLED_APPS = [
...
z_app.apps.ZAppConfig,
]

二级菜单

二级菜单就创建一个模型, 这里要注意设置 managed = False, 这样就不会在数据库里面创建表.
但是默认把模型的4个权限插入了权限表, 后面可以用来做权限校验支持.

1
2
3
4
5
6
7
# models.py
class UploadMaterial(models.Model):
class Meta:
# 注意设置
managed = False
verbose_name = "上传素材"
verbose_name_plural = verbose_name

因为二级菜单点击的链接对应的是 changelist_view 方法, 所以我们修改该方法.
通过查看admin.ModelAdmin 的这个方法实现, 保留了权限的校验代码, 这样就可以继承菜单权限的校验.

1
2
3
4
5
6
7
8
9
10
11
12
# admin.py
from .models import UploadMaterial

@admin.register(UploadMaterial)
class UploadMaterialAdmin(admin.ModelAdmin):
def changelist_view(self, request, extra_context=None):
# 权限校验
if not self.has_view_or_change_permission(request):
raise PermissionDenied

# 定制页面
return render(request, 'z_app/uploadmaterial.html')

创建自定义页面

1
2
# z_app/templates/z_app/uploadmaterial.html
xxxxx

效果

就用文字来简单描述下效果

1
2
自定义菜单    
上传素材

ubuntu20 默认使用 mysql-server 安装的是mysql8,所以需要额外操作.

安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
wget https://dev.mysql.com/get/mysql-apt-config_0.8.12-1_all.deb
# 选择 Bionic, mysql5.7
dpkg -i mysql-apt-config_0.8.12-1_all.deb

apt-get update

# 查看各个版本,看有没有5.7的
apt-cache policy mysql-server

# 安装
apt install -f mysql-client=5.7* mysql-community-server=5.7* mysql-server=5.7*

# 配置
mysql_secure_installation

# 连接测试
mysql -u root -p
SELECT VERSION()

配置(可选)

远程访问

1
2
3
vim /etc/mysql/mysql.conf.d/mysqld.cnf

bind-address = 0.0.0.0

创建新用户

1
2
3
CREATE USER 'user'@'%' IDENTIFIED BY 'MyStrongPass.';
GRANT ALL PRIVILEGES ON * . * TO 'user'@'%';
FLUSH PRIVILEGES;

修改密码

1
2
格式:mysqladmin -u用户名 -p旧密码 password 新密码 
例子:mysqladmin -uroot -p123456 password 123

一些坑

坑1: 先安装高版本导致低版本启动不了

因为先安装了mysql8, 卸载后安装mysql5.8 一直报错,启动不了, 报错信息很少, 最后是在日志文件 /var/log/mysqld.log 中发现 [ERROR] [FATAL] InnoDB: Table flags are 0 in the data dictionary but the flags in file ./ibdata1 are 0x4800! 这样的报错信息,搜索发现高版本的mysql文件未卸载干净,删掉 /var/lib/mysql 整个目录 重新安装就可以了.

坑2: 程序连不到mysql

程序里面用的是127.0.0.1, 报错说连不到root@localhost

1
select user, host, plugin, authentication_string from mysql.user;

发现 root 用户不是密码登录, 可以修改为密码登录的

1
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'xxx';

参考链接

发布订阅模式有很多名字,例如观察者模式.
观察者模式是一种行为设计模式,允许你定义一种订阅机制,可在对象事件发生时通知多个”观察”该对象的其他对象.

例子

发布订阅模式在编程里面很常见.下面举些例子.

javascrip

在js 里面经常用到的

  • addEventListener
  • dispatchEvent
  • removeEventListener

websocket

  • open
  • message
  • error
  • close
1
2
3
4
5
6
7
8
9
10
11
12
// Create WebSocket connection.
const socket = new WebSocket('ws://localhost:8080');

// Connection opened
socket.addEventListener('open', function (event) {
socket.send('Hello Server!');
});

// Listen for messages
socket.addEventListener('message', function (event) {
console.log('Message from server ', event.data);
});

Vue

  • vm.$on
  • vm.$once
  • vm.$off
  • vm.$emit
    1
    2
    3
    4
    5
    vm.$on('test', function (msg) {
    console.log(msg)
    })
    vm.$emit('test', 'hi')
    // => "hi"

即时通信 IM SDK

  • on
  • off
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
tim.on(TIM.EVENT.MESSAGE_RECEIVED, function(event) {
// 收到推送的单聊、群聊、群提示、群系统通知的新消息,可通过遍历 event.data 获取消息列表数据并渲染到页面
// event.name - TIM.EVENT.MESSAGE_RECEIVED
// event.data - 存储 Message 对象的数组 - [Message]
});

tim.on(TIM.EVENT.MESSAGE_MODIFIED, function(event)) {
// 收到消息被第三方回调修改的通知,消息发送方可通过遍历 event.data 获取消息列表数据并更新页面上同 ID 消息的内容(v2.12.1起支持)
// event.name - TIM.EVENT.MESSAGE_MODIFIED
// event.data - 存储被第三方回调修改过的 Message 对象的数组 - [Message]
});

tim.on(TIM.EVENT.MESSAGE_REVOKED, function(event) {
// 收到消息被撤回的通知。使用前需要将SDK版本升级至v2.4.0或更高版本
// event.name - TIM.EVENT.MESSAGE_REVOKED
// event.data - 存储 Message 对象的数组 - [Message] - 每个 Message 对象的 isRevoked 属性值为 true
});

tim.on(TIM.EVENT.MESSAGE_READ_BY_PEER, function(event) {
// SDK 收到对端已读消息的通知,即已读回执。使用前需要将SDK版本升级至v2.7.0或更高版本。仅支持单聊会话
// event.name - TIM.EVENT.MESSAGE_READ_BY_PEER
// event.data - event.data - 存储 Message 对象的数组 - [Message] - 每个 Message 对象的 isPeerRead 属性值为 true
});

tim.on(TIM.EVENT.CONVERSATION_LIST_UPDATED, function(event) {
// 收到会话列表更新通知,可通过遍历 event.data 获取会话列表数据并渲染到页面
// event.name - TIM.EVENT.CONVERSATION_LIST_UPDATED
// event.data - 存储 Conversation 对象的数组 - [Conversation]
});

核心内容

一个发布者,一个或多个订阅者

  • 一个订阅的方法
  • 一个发布的方法
  • 一个取消订阅的方法

如何通用实现

也可以看 [JavaScript设计模式与开发实践] 这本书里面的 “第8章 发布—订阅模式”, 里面说的比较细

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
// event.js

class Event {

/**
* on 方法把订阅者所想要订阅的事件及相应的回调函数记录在 Event 对象的 _cbs 属性中
*/
on (event, fn) {
if (typeof fn != "function") {
console.error('fn must be a function')
return
}

this._cbs = this._cbs || {}
;(this._cbs[event] = this._cbs[event] || []).push(fn)
}


/**
* emit 方法接受一个事件名称参数,在 Event 对象的 _cbs 属性中取出对应的数组,并逐个执行里面的回调函数
*/
emit (event) {
this._cbs = this._cbs || {}
var callbacks = this._cbs[event], args

if (callbacks) {
callbacks = callbacks.slice(0)
args = [].slice.call(arguments, 1)
for (var i = 0, len = callbacks.length; i < len; i++) {
callbacks[i].apply(null, args)
}
}
}



/**
* off 方法接受事件名称和当初注册的回调函数作参数,在 Event 对象的 _cbs 属性中删除对应的回调函数。
*/
off (event, fn) {
this._cbs = this._cbs || {}

// all
if (!arguments.length) {
this._cbs = {}
return
}

var callbacks = this._cbs[event]
if (!callbacks) return

// remove all handlers
if (arguments.length === 1) {
delete this._cbs[event]
return
}

// remove specific handler
var cb
for (var i = 0, len = callbacks.length; i < len; i++) {
cb = callbacks[i]
if (cb === fn || cb.fn === fn) {
callbacks.splice(i, 1)
break
}
}
return
}
}

参考链接

今天在 githubpython Trending 看到一个叫 benbusby/whoogle-search 的项目. 它是一个自部署,无广告,隐私保护的元搜索引擎.
点击查看demo http://search.jyt321.com

安装

最简单的就是用 docker 来安装了

1
2
docker pull benbusby/whoogle-search
docker run --publish 5000:5000 --detach --name whoogle-search benbusby/whoogle-search:latest

在配置 nginx, 绑定域名, 就有一个属于你自己网址的搜索引擎了.

问题

django 自带的后台管理比较丑, 就使用了 simpleui 的主题. 为了后台的安全, 需要增加登录验证码.网上有些零散的教程,实际用起来非常不方便.就想把该功能做出一个可复用的django app, 方便后续使用.开源出来,也方便大家使用.

成果

django-simpleui-captcha 是一个 django 后台管理登录验证码插件.

界面

screenshoot1.png

安装

1
pip install django-simpleui-captcha

快速开始

1. 添加 “simpleui_captcha” 到 INSTALLED_APPS 设置, 注意要放在最前面

1
2
3
4
5
INSTALLED_APPS = [
"simpleui_captcha",
"simpleui",
...
]

2. 添加 simpleui_captchaurl 到你的项目 urls.py ::

1
path('simpleui_captcha/', include('simpleui_captcha.urls')),

3. 运行 python manage.py migrate 迁移验证码模型

开源过程

起名

基于django web框架, 用的是simpleui 主题, 实现验证码功能, 故而叫django-simpleui-captcha, 让人一看名字就知道是做什么的,简单明了.

实现思路

站在巨人的肩膀上,直接集成 django-simple-captcha,添加验证码到登录表单,在修改成simpleui的样式.

登录表单

后台的登录表单是AdminAuthenticationForm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# django.contrib.sites.py
class AdminSite:
def get_urls(self):
# Admin-site-wide views.
urlpatterns = [
path('login/', self.login, name='login'),
path('logout/', wrap(self.logout), name='logout'),
...
]
def login(self, request, extra_context=None):
defaults = {
'extra_context': context,
'authentication_form': self.login_form or AdminAuthenticationForm,
'template_name': self.login_template or 'admin/login.html',
}

所以我们需要继承该表单,添加验证码字段

1
2
3
4
5
6
7
8
9
# simpleui_captcha.forms.py
from captcha.fields import CaptchaField
from django.contrib.admin.forms import AdminAuthenticationForm


class MultiCaptchaAdminAuthenticationForm(AdminAuthenticationForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['captcha'] = CaptchaField()

然后赋值给站点的登录表单变量

1
2
3
4
5
6
# simpleui_captcha.admin.py
from django.contrib import admin

from .forms import MultiCaptchaAdminAuthenticationForm

admin.AdminSite.login_form = MultiCaptchaAdminAuthenticationForm

验证码相关url

用来刷新验证码

1
2
3
4
5
6
# simpleui_captcha.urls.py
from django.urls import path, include

urlpatterns = [
path('captcha/', include('captcha.urls')),
]

模板修改

扩展后台登录模板文件,把验证码字段加入表单,并加入刷新验证码功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# simpleui_captcha/templates/admin/login.html
{% extends "admin/login.html" %}

<script>
window.onload = function () {
let captchaEle = document.querySelector("img.captcha");
captchaEle.onclick = function () {
$.getJSON("/simpleui_captcha/captcha/refresh/", function (result) {
$('img.captcha').attr('src', result['image_url']);
$('#id_captcha_0').val(result['key'])
});
};
...
</script>

{% block form %}
{{ block.super }}
<div hidden="hidden">
{{ form.captcha }}
</div>
{% endblock %}

发布

按照django官方文档-进阶指南:如何编写可重用程序 的介绍, 完成项目,打包应用,发布应用.最后还可以提交到github上,让大家来贡献代码. 点击 django-simpleui-captcha 给个Star 吧.