Dust8 的博客

读书百遍其义自见

0%

起源是在列表页使用了 list_editable 来编辑字段,出现金额应该占用很小的列宽,实际上input导致很宽,字段多的时候就会出现横向的滚动条,导致不好查看.

官方文档

list_display 中的字段名也会以 CSS 类的形式出现在 HTML 输出中,在每个 元素上以 column-<field_name> 的形式出现。例如,这可以用来在 CSS 文件中设置列宽。

1
2
3
4
5
6
class ArticleAdmin(admin.ModelAdmin):
class Media:
css = {
"all": ("my_styles.css",)
}
js = ("my_code.js",)

实际使用修改input

1
2
3
4
5
6
# admin.py
class ProductSKUAdmin(admin.ModelAdmin):
class Media:
css = {
"all": ("css/productsku.css",)
}
1
2
3
4
# css/productsku.css
.results input{
max-width:80px;
}

reslult 这个css类名可以通过浏览的开发者工具查看源代码获得.
同理你想其他的样式也可以.

参考链接

最近要分析下用户登录地域.了解到ECharts可以实现,可以看官方的例子
香港18区人口密度.

数据

用户数据

在登陆的时候用GPS定位,或者ip地址获取到地理位置,例如省的名称, 记录到数据库. 然后把数据按省来分组用户去重就可以得到echarts地图需要的数据了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
qs = (UserLoginRecord.objects.filter(record_time__gte=start_date)
.filter(record_time__lt=end_date)
.values('province')
.annotate(unique_user=Count('user_id', distinct=True))
.order_by('unique_user'))
# 结果
[
{ province: '北京', unique_user: 20 },
{ province: '台湾', unique_user: 1 },
]

# 转换为
[
{ name: '北京', value: 20 },
{ name: '台湾', value: 1 },
]

地图数据

2种方式,用别人处理好的, 或者自己画(看参考链接).
v5 移除了内置的 geoJSON(原先在 echarts/map 文件夹下)。这些 geoJSON 文件本就一直来源于第三方。如果使用者仍然需要他们,可以去从老版本中得到,或者自己寻找更合适的数据然后通过 registerMap 接口注册到 ECharts 中。
这里我从第三方的cdn里面找到老版本的json文件.

1
2
# utils/china.js, 把json地图文件内容变成变量好引入,就不用发请求了
export const chinaJson = { "type": "FeatureCollection", ...}

1
2
3
# 导入地图数据并注册
import { chinaJson } from '/@/utils/china'
echarts.registerMap('china', chinaJson)

数据处理好了,其他的看例子就好了

参考链接

event-tracking 是一个事件跟踪库, 为后续的分析提供原始数据.位置如下图所示.

怎么使用不多说,可以看示例; 也可以去edx的其他代码库搜索它们怎么用的. 额外说下事件的命名,是由点号分割来避免冲突,规则为namespace.object.action(https://event-tracking.readthedocs.io/en/latest/user_guide/design.html#best-practices, https://edx.readthedocs.io/projects/edx-developer-guide/en/latest/analytics.html#naming-events).

event-tracking

  • https://github.com/edx/event-tracking
  • https://event-tracking.readthedocs.io/en/latest/overview.html
  • https://edx.readthedocs.io/projects/edx-developer-guide/en/latest/analytics.html

    tracker

    • Tracker
      • 追踪器
      • emit
        • 提交事件.emit(name=None, data=None)
        • name为事件名,像’edx.course.enrollment.activated’,
          可以用’namespace.object.action’结构;
          data为事件,类型为可以序列化的字典
    • DjangoTracker
      • 从django配置初始化的追踪器,继承Tracker
    • register_tracker
    • get_tracker
    • emit

      locator

    • DefaultContextLocator
    • ThreadLocalContextLocator

      backend

    • 事件追踪后端,必须提供一个send(event)方法,用来处理接收到的事件
    • LoggerBackend
      • 使用python的logger的后端,事件使用logger处理
    • MongoBackend
      • 使用mongodb数据库的后端,事件插入数据库,默认是eventtracking库的events表
    • RoutingBackend
      • 将事件路由到适当的后端
      • Processors
        • 处理器集合.管道处理,类似责任链模式
      • backends
        • 处理后端集合,可以是所有的处理后端,包括RoutingBackend自身
    • AsyncRoutingBackend
      • 异步路由后端继承RoutingBackend,使用了celery来处理
    • SegmentBackend
      • 将事件发送到segment.com的后端

processor

  • 处理器,用来过滤事件
  • RegexFilter
    • 正则处理器
  • NameWhitelistProcessor:
    • 白名单处理器

参考链接

写了个golang程序来根据当前ip自动修改阿里云ecs的安全组.现在需要在手机运行,找了下资料实现了.
android未root的情况下.

编译出android环境的程序

把main.go文件编译出anroid arm64的程序.大部分android都是arm64, 可以在anroid上运行getprop ro.product.cpu.abi查看具体架构.

1
2
3
4
set CGO_ENABLED=0
set GOOS=linux
set GOARCH=arm64
go build -o xxx main.go

运行环境termux

termux 是免费的android终端模拟器.
打开termux, 安装下面的包.

安装termux-setup-storage

默认工作目录是termux的安装目录,想要获取手机存储则需要安装它.
在终端里面输入 termux-setup-storage, 安装好了后termux主目录会生成 storage 子目录,这时我们就可以访问手机存储了

安装termux-chroot

termux没有传统的linux文件系统, 例如/etc ,这个可以设置一个最小的正常的文件系统.
这样就可以通过修改文件/etc/resolv.conf 来设置 dns

1
pkg install termux-chroot

安装vim

golang程序里面有网络访问, 在termux里面报read: connection refused 错误,
所以要用它来改dns设置

1
pkg install vim

设置dns

1
2
3
4
vim /etc/resolv.conf

# /etc/resolv.conf
nameserver 8.8.8.8

运行golang

在手机存储目录是没有权限的,需要把程序复制到termux目录

1
2
cp xxx ~/xxx
./xxx

这样就好了

源代码

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# main.go
package main

import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"path"
"strings"
"time"

"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
)

type ConfigurationModel struct {
ACCESS_KEY_ID string
ACCESS_KEY_SECRET string
REGION_ID string
SECURITY_GROUP_ID string
IP_PROTOCOL string
PORT_RANGE string
DESCRIPTION string
DURATION int
}

type IpResponse struct {
Origin string `json:"origin"`
}

var configModel ConfigurationModel

// 公网 IP 查询站点的 URL 地址。
const GetPublicIpUrl = "http://www.httpbin.org/ip"

func main() {
loadConfig()
fmt.Println("配置:", configModel.SECURITY_GROUP_ID)

filename := configModel.SECURITY_GROUP_ID + "_ip.txt"
oldIp := readIp(filename)
fmt.Printf("最后IP记录为: %s\n", oldIp)

ecsClient, err := ecs.NewClientWithAccessKey(configModel.REGION_ID, configModel.ACCESS_KEY_ID, configModel.ACCESS_KEY_SECRET)
if err != nil {
// Handle exceptions
panic(err)
}

for {
newIp := getPublicIp()
if oldIp != newIp {
fmt.Printf("IP变更: %s => %s\n", oldIp, newIp)

delSecurityGroup(ecsClient, configModel, oldIp)
addSecurityGroup(ecsClient, configModel, newIp)

oldIp = newIp
writeIp(filename, newIp)
}

time.Sleep(time.Duration(configModel.DURATION) * time.Second)
}

}

func loadConfig() {
var configFile string

dir, _ := os.Getwd()
configFile = path.Join(dir, "settings.json")

// 打开配置文件,并进行反序列化。
f, err := os.Open(configFile)
if err != nil {
log.Fatalf("无法打开文件:%s", err)
os.Exit(-1)
}
defer f.Close()
data, _ := ioutil.ReadAll(f)

if err := json.Unmarshal(data, &configModel); err != nil {
log.Fatalf("数据反序列化失败:%s", err)
os.Exit(-1)
}
}

func getPublicIp() string {
resp, err := http.Get(GetPublicIpUrl)
if err != nil {
log.Printf("获取公网 IP 出现错误,错误信息:%s", err)
os.Exit(-1)
}
defer resp.Body.Close()

bytes, _ := ioutil.ReadAll(resp.Body)

var res IpResponse
if err := json.Unmarshal(bytes, &res); err != nil {
log.Fatalf("数据反序列化失败:%s", err)
os.Exit(-1)
}

return res.Origin
}

func delSecurityGroup(client *ecs.Client, configModel ConfigurationModel, ip string) {
request := ecs.CreateRevokeSecurityGroupRequest()
request.SecurityGroupId = configModel.SECURITY_GROUP_ID
request.PortRange = configModel.PORT_RANGE
request.IpProtocol = configModel.IP_PROTOCOL
request.SourceCidrIp = ip

response, err := client.RevokeSecurityGroup(request)
if err != nil {
// 异常处理
panic(err)
}
fmt.Printf("删除安全组成功: (%d)! %s\n", response.GetHttpStatus(), ip)
}

func addSecurityGroup(client *ecs.Client, configModel ConfigurationModel, ip string) {
request := ecs.CreateAuthorizeSecurityGroupRequest()
request.SecurityGroupId = configModel.SECURITY_GROUP_ID
request.PortRange = configModel.PORT_RANGE
request.IpProtocol = configModel.IP_PROTOCOL
request.Description = configModel.DESCRIPTION
request.SourceCidrIp = ip

response, err := client.AuthorizeSecurityGroup(request)
if err != nil {
// 异常处理
panic(err)
}
fmt.Printf("添加安全组成功: (%d)! %s\n", response.GetHttpStatus(), ip)
}

func readIp(filename string) string {
ip := "127.0.0.1"

contents, err := ioutil.ReadFile(filename)
if err == nil {
//因为contents是[]byte类型,直接转换成string类型后会多一行空格,需要使用strings.Replace替换换行符
ip = strings.Replace(string(contents), "\n", "", 1)
}

return ip
}
func writeIp(filename string, content string) {
data := []byte(content)
ioutil.WriteFile(filename, data, 0644)
}
1
2
3
4
5
6
7
8
9
10
11
# settings.json
{
"ACCESS_KEY_ID": "xx",
"ACCESS_KEY_SECRET": "xx",
"REGION_ID": "cn-shenzhen",
"SECURITY_GROUP_ID": "xx",
"IP_PROTOCOL": "tcp",
"PORT_RANGE": "1/65535",
"DESCRIPTION": "白名单ip",
"DURATION": 60
}

参考链接

django-ckeditor/django-ckeditor是django后台的富文本插件,没有默认带视频编辑功能,而我要用到,找了下资料实现了.

原理

1.下载ckeditor的插件
2.django里面配置启用插件

这里面资料少的是如何把他们合理的结合在一起.

示例

下载插件

django-ckeditor 使用的是 ckeditor4, 去它的 github 可以看到 Features 里面说有超过 500 个插件,并给出了插件地址 https://ckeditor.com/cke4/addons/plugins/all.
在里面搜索 video 可以看到相关的一些插件. 这里我选用了 html5video 这个插件, 把它下载并解压出来.

启用插件

django-ckeditor 的文档里面有说明

1
2
3
4
5
6
7
8
CKEDITOR_CONFIGS = {
'default': {
'extraPlugins': ','.join([
...
'html5video',
]),
}
}

结合

这里是重点,下载的插件放到哪里?
先启用插件,去后台看下,因为还不知道放哪里就没放,所以会报找不到文件的错误,在浏览器开发控制台看到是
static的目录里面,也有具体的路径了,是/static/ckeditor/ckeditor/plugins/html5video.
我们也不能直接放到static目录里面,因为开发环境是用不了的,所以建了一个和它同级的目录static_common,
所以最后的目录是/static_common/ckeditor/ckeditor/plugins/html5video.
然后修改配置

1
2
3
4
5
STATIC_URL = "/static/"
STATIC_ROOT = os.path.join(BASE_DIR, "static")
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static_common"),
]

这样就可以了, 这几个参数的作用可以看文末的参考链接.
网上有些资料是把插件放到python包的路径,虽然能实现,但是是不可取的.

参考链接