django-kv-config-widget 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. django_kv_config_widget-0.1.0/LICENSE +21 -0
  2. django_kv_config_widget-0.1.0/PKG-INFO +235 -0
  3. django_kv_config_widget-0.1.0/README.md +208 -0
  4. django_kv_config_widget-0.1.0/django_kv_config_widget/__init__.py +0 -0
  5. django_kv_config_widget-0.1.0/django_kv_config_widget/apps.py +6 -0
  6. django_kv_config_widget-0.1.0/django_kv_config_widget/fields.py +33 -0
  7. django_kv_config_widget-0.1.0/django_kv_config_widget/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
  8. django_kv_config_widget-0.1.0/django_kv_config_widget/locale/zh_Hans/LC_MESSAGES/django.po +88 -0
  9. django_kv_config_widget-0.1.0/django_kv_config_widget/static/django_kv_config_widget/css/django_kv_config_widget.css +50 -0
  10. django_kv_config_widget-0.1.0/django_kv_config_widget/static/django_kv_config_widget/js/django_kv_config_widget.js +336 -0
  11. django_kv_config_widget-0.1.0/django_kv_config_widget/templates/django_kv_config_widget/widgets/key_value.html +58 -0
  12. django_kv_config_widget-0.1.0/django_kv_config_widget/widgets.py +162 -0
  13. django_kv_config_widget-0.1.0/django_kv_config_widget.egg-info/PKG-INFO +235 -0
  14. django_kv_config_widget-0.1.0/django_kv_config_widget.egg-info/SOURCES.txt +32 -0
  15. django_kv_config_widget-0.1.0/django_kv_config_widget.egg-info/dependency_links.txt +1 -0
  16. django_kv_config_widget-0.1.0/django_kv_config_widget.egg-info/requires.txt +2 -0
  17. django_kv_config_widget-0.1.0/django_kv_config_widget.egg-info/top_level.txt +3 -0
  18. django_kv_config_widget-0.1.0/django_kv_config_widget_demo/__init__.py +0 -0
  19. django_kv_config_widget-0.1.0/django_kv_config_widget_demo/asgi.py +5 -0
  20. django_kv_config_widget-0.1.0/django_kv_config_widget_demo/settings.py +67 -0
  21. django_kv_config_widget-0.1.0/django_kv_config_widget_demo/urls.py +6 -0
  22. django_kv_config_widget-0.1.0/django_kv_config_widget_demo/wsgi.py +5 -0
  23. django_kv_config_widget-0.1.0/django_kv_config_widget_example/__init__.py +0 -0
  24. django_kv_config_widget-0.1.0/django_kv_config_widget_example/admin.py +40 -0
  25. django_kv_config_widget-0.1.0/django_kv_config_widget_example/apps.py +6 -0
  26. django_kv_config_widget-0.1.0/django_kv_config_widget_example/forms.py +33 -0
  27. django_kv_config_widget-0.1.0/django_kv_config_widget_example/migrations/0001_initial.py +50 -0
  28. django_kv_config_widget-0.1.0/django_kv_config_widget_example/migrations/0002_kvconfigmodel_config2_alter_kvconfigmodel_config.py +25 -0
  29. django_kv_config_widget-0.1.0/django_kv_config_widget_example/migrations/0003_alter_kvconfigmodel_config_and_more.py +36 -0
  30. django_kv_config_widget-0.1.0/django_kv_config_widget_example/migrations/__init__.py +0 -0
  31. django_kv_config_widget-0.1.0/django_kv_config_widget_example/models.py +17 -0
  32. django_kv_config_widget-0.1.0/django_kv_config_widget_example/views.py +20 -0
  33. django_kv_config_widget-0.1.0/pyproject.toml +42 -0
  34. django_kv_config_widget-0.1.0/setup.cfg +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 rRR0VrFP
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,235 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-kv-config-widget
3
+ Version: 0.1.0
4
+ Summary: A Django widget for managing key-value configuration pairs with dynamic add/remove support
5
+ Author-email: rRR0VrFP <rrr0vrfp@qq.com>
6
+ Maintainer-email: rRR0VrFP <rrr0vrfp@qq.com>
7
+ License: MIT
8
+ Project-URL: homepage, https://gitee.com/rRR0VrFP/django-kv-config-widget
9
+ Keywords: django,widget,key-value,config,admin
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Framework :: Django
12
+ Classifier: Framework :: Django :: 4.2
13
+ Classifier: Framework :: Django :: 5.0
14
+ Classifier: Framework :: Django :: 5.1
15
+ Classifier: Framework :: Django :: 5.2
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: Django>=4.2
25
+ Requires-Dist: PyYAML>=6.0
26
+ Dynamic: license-file
27
+
28
+ # django-kv-config-widget
29
+
30
+ django-kv-config-widget 是一个专为 Django 后台设计的键值对(Key-Value)配置编辑组件。它提供表格化界面,帮助非技术用户直观管理 JSON 配置数据,有效避免手工编辑原始 JSON 可能引发的格式错误。
31
+
32
+ ![Version](https://img.shields.io/badge/version-0.1.0-blue)
33
+ ![Django](https://img.shields.io/badge/Django-4.2%20%7C%205.0%20%7C%205.1%20%7C%205.2-green)
34
+ ![License](https://img.shields.io/badge/License-MIT-green)
35
+
36
+ ## 特性
37
+
38
+ - 动态添加/删除行
39
+ - 多行值输入,自动撑高
40
+ - **类型系统** — `str`、`text`、`number`、`int`、`bool`(NullableBoolean)、`json`,自动切换控件,提交时校验类型
41
+ - **`default_keys`** — 预设键名,支持默认值、占位提示、类型
42
+ - **`required_keys`** — 必填键,值为空时阻止提交
43
+ - **`allow_custom`** — 设为 `False` 时键以下拉选择,不可重复,所有键用完后自动隐藏添加按钮
44
+ - **导入/导出** — 粘贴 YAML、JSON 或 `KEY=VALUE` 文本;一键导出为 YAML
45
+ - **Enter** 在键输入框跳转到值输入框;在值文本框内换行
46
+ - 国际化(默认英文,设置 `LANGUAGE_CODE=zh-hans` 切换中文)
47
+ - Null 安全,跟随 Django CSS 变量主题,系统焦点环
48
+ - 兼容 `JSONField` 和 `TextField`
49
+ - 纯 CSS,无外部依赖
50
+
51
+ ## 安装
52
+
53
+ ```bash
54
+ pip install django-kv-config-widget
55
+ ```
56
+
57
+ 在 `INSTALLED_APPS` 中注册:
58
+
59
+ ```python
60
+ INSTALLED_APPS = [
61
+ ...
62
+ "django_kv_config_widget",
63
+ ]
64
+ ```
65
+
66
+ ## 用法
67
+
68
+ ### 推荐:使用 KVConfigFormField(自动校验)
69
+
70
+ ```python
71
+ from django_kv_config_widget.fields import KVConfigFormField
72
+
73
+
74
+ class ConfigForm(forms.Form):
75
+ settings = KVConfigFormField(
76
+ label="应用配置",
77
+ default_keys=[
78
+ {"key": "DB_URL", "type": "str", "default": "postgres://localhost:5432/app", "placeholder": "数据库连接地址"},
79
+ {"key": "DEBUG", "type": "bool", "default": "true"},
80
+ {"key": "PORT", "type": "int", "default": "8080"},
81
+ {"key": "ALLOWED_HOSTS", "type": "json", "placeholder": '["localhost", "example.com"]'},
82
+ "LOG_LEVEL",
83
+ ],
84
+ required_keys=["DB_URL"],
85
+ allow_custom=False,
86
+ help_text="仅允许预定义的键,DB_URL 为必填项。",
87
+ )
88
+ ```
89
+
90
+ ### 直接使用 Widget(手动校验)
91
+
92
+ ```python
93
+ from django import forms
94
+ from django_kv_config_widget.widgets import DjangoKVConfigWidget
95
+
96
+
97
+ class ConfigForm(forms.Form):
98
+ settings = forms.JSONField(
99
+ widget=DjangoKVConfigWidget(
100
+ default_keys=[
101
+ {"key": "DB_URL", "type": "str", "default": "postgres://localhost/app"},
102
+ {"key": "PORT", "type": "int", "default": "8080"},
103
+ ],
104
+ required_keys=["DB_URL"],
105
+ ),
106
+ )
107
+
108
+ def clean_settings(self):
109
+ value = self.cleaned_data.get("settings") or {}
110
+ widget = self.fields["settings"].widget
111
+ DjangoKVConfigWidget.validate_required_keys(value, widget.required_keys)
112
+ DjangoKVConfigWidget.validate_types(value, DjangoKVConfigWidget.get_type_map(widget.default_keys))
113
+ if not widget.allow_custom:
114
+ allowed = {dk["key"] for dk in widget.default_keys}
115
+ for k in value:
116
+ if k not in allowed:
117
+ raise forms.ValidationError("'%s' 不在预定义键中" % k)
118
+ return value
119
+ ```
120
+
121
+ ### 在 Django Admin 中使用
122
+
123
+ ```python
124
+ from django.contrib import admin
125
+ from django import forms
126
+ from django_kv_config_widget.fields import KVConfigFormField
127
+ from .models import MyModel
128
+
129
+
130
+ class MyModelForm(forms.ModelForm):
131
+ config = KVConfigFormField(
132
+ label="配置",
133
+ default_keys=[{"key": "DB_URL", "type": "str", "default": "postgres://localhost/app"}],
134
+ required_keys=["DB_URL"],
135
+ allow_custom=False,
136
+ )
137
+ class Meta:
138
+ model = MyModel
139
+ fields = "__all__"
140
+
141
+
142
+ @admin.register(MyModel)
143
+ class MyModelAdmin(admin.ModelAdmin):
144
+ form = MyModelForm
145
+ ```
146
+
147
+ ### `default_keys` 格式
148
+
149
+ | 形式 | 示例 | 效果 |
150
+ |------|------|------|
151
+ | `str` | `"LOG_LEVEL"` | 文本输入框,无默认值 |
152
+ | `dict` | `{"key":"PORT","type":"int","default":"8080","placeholder":"端口号"}` | 类型化输入,有默认值 + 占位提示 |
153
+
154
+ ### 类型对照
155
+
156
+ | 类型 | 控件 | 值 |
157
+ |------|------|-----|
158
+ | `str` | textarea(单行外观) | 任意文本,支持换行 |
159
+ | `text` | textarea | 任意文本,支持换行 |
160
+ | `number` | `<input type="number">` | 浮点数 |
161
+ | `int` | `<input type="number" step="1">` | 整数 |
162
+ | `bool` | `<select>` | 空 / `true` / `false`(三态) |
163
+ | `json` | textarea | 合法 YAML/JSON |
164
+
165
+ ### 导入 / 导出
166
+
167
+ 点击 **Import** 粘贴 YAML、JSON 或 `KEY=VALUE` 文本,自动解析填充表格。
168
+ 点击 **Export** 将当前数据导出为 YAML。
169
+
170
+ ### 国际化
171
+
172
+ 默认界面为英文。设置以下内容可切换为中文:
173
+
174
+ ```python
175
+ LANGUAGE_CODE = "zh-hans"
176
+ ```
177
+
178
+ 校验错误信息和占位提示均已翻译。
179
+
180
+ ## API
181
+
182
+ ### `DjangoKVConfigWidget`
183
+
184
+ | 参数 | 类型 | 默认值 | 说明 |
185
+ |------|------|--------|------|
186
+ | `default_keys` | `list[str \| dict]` | `[]` | 预定义键。`str` 为简单键名;`dict` 支持 `key`、`default`、`placeholder`、`type` |
187
+ | `required_keys` | `list[str]` | `[]` | 必填键列表,值为空时提交不通过 |
188
+ | `allow_custom` | `bool` | `True` | 是否允许自定义键。`False` 时键以下拉选择,不可重复,用完后隐藏添加按钮 |
189
+ | `attrs` | `dict` | `None` | 标准 Django widget HTML 属性 |
190
+
191
+ 静态方法:
192
+ - `validate_required_keys(value, required_keys)` — 必填键为空时抛出 `ValidationError`
193
+ - `validate_types(value, type_map)` — 类型不匹配时抛出 `ValidationError`
194
+ - `get_type_map(default_keys)` → `dict` — 构建 `{键名: 类型}` 映射
195
+
196
+ ### `KVConfigFormField(forms.JSONField)`
197
+
198
+ 自动校验必填键、类型、自定义键限制。接收 `default_keys`、`required_keys`、`allow_custom` 参数(透传给内部 widget)。
199
+
200
+ ## 数据格式
201
+
202
+ 提交时以 JSON 对象形式存储。空键被丢弃。空提交返回 `""`。
203
+
204
+ ```json
205
+ {"KEY1": "value1", "KEY2": "value2"}
206
+ ```
207
+
208
+ ## 开发
209
+
210
+ ```bash
211
+ git clone ...
212
+ cd django-kv-config-widget
213
+ pip install -r requirements.txt
214
+ python manage.py migrate
215
+ python manage.py createsuperuser
216
+ python manage.py runserver
217
+ ```
218
+
219
+ - 管理后台:http://127.0.0.1:8000/admin/
220
+
221
+ ## 更新记录
222
+
223
+ ### v0.1.0
224
+
225
+ 1. 动态添加/删除行,多行值自动撑高
226
+ 2. `default_keys` 支持 dict 格式(`default`、`placeholder`、`type`)
227
+ 3. 类型系统:`str`、`text`、`number`、`int`、`bool`(NullableBoolean)、`json`
228
+ 4. `required_keys` — 必填键为空时阻止提交
229
+ 5. `allow_custom` — 预定义键下拉选择;禁止重复;用完后隐藏添加按钮
230
+ 6. `KVConfigFormField` — 内置必填 + 类型 + 自定义键校验
231
+ 7. 导入/导出 — YAML、JSON、KEY=VALUE
232
+ 8. 键盘:Enter 键→值跳转;Enter 值内换行
233
+ 9. 国际化(英文默认,中文通过 `LANGUAGE_CODE=zh-hans`)
234
+ 10. 系统焦点环、Django CSS 变量主题
235
+ 11. `validate_required_keys`、`validate_types`、`get_type_map` 静态方法
@@ -0,0 +1,208 @@
1
+ # django-kv-config-widget
2
+
3
+ django-kv-config-widget 是一个专为 Django 后台设计的键值对(Key-Value)配置编辑组件。它提供表格化界面,帮助非技术用户直观管理 JSON 配置数据,有效避免手工编辑原始 JSON 可能引发的格式错误。
4
+
5
+ ![Version](https://img.shields.io/badge/version-0.1.0-blue)
6
+ ![Django](https://img.shields.io/badge/Django-4.2%20%7C%205.0%20%7C%205.1%20%7C%205.2-green)
7
+ ![License](https://img.shields.io/badge/License-MIT-green)
8
+
9
+ ## 特性
10
+
11
+ - 动态添加/删除行
12
+ - 多行值输入,自动撑高
13
+ - **类型系统** — `str`、`text`、`number`、`int`、`bool`(NullableBoolean)、`json`,自动切换控件,提交时校验类型
14
+ - **`default_keys`** — 预设键名,支持默认值、占位提示、类型
15
+ - **`required_keys`** — 必填键,值为空时阻止提交
16
+ - **`allow_custom`** — 设为 `False` 时键以下拉选择,不可重复,所有键用完后自动隐藏添加按钮
17
+ - **导入/导出** — 粘贴 YAML、JSON 或 `KEY=VALUE` 文本;一键导出为 YAML
18
+ - **Enter** 在键输入框跳转到值输入框;在值文本框内换行
19
+ - 国际化(默认英文,设置 `LANGUAGE_CODE=zh-hans` 切换中文)
20
+ - Null 安全,跟随 Django CSS 变量主题,系统焦点环
21
+ - 兼容 `JSONField` 和 `TextField`
22
+ - 纯 CSS,无外部依赖
23
+
24
+ ## 安装
25
+
26
+ ```bash
27
+ pip install django-kv-config-widget
28
+ ```
29
+
30
+ 在 `INSTALLED_APPS` 中注册:
31
+
32
+ ```python
33
+ INSTALLED_APPS = [
34
+ ...
35
+ "django_kv_config_widget",
36
+ ]
37
+ ```
38
+
39
+ ## 用法
40
+
41
+ ### 推荐:使用 KVConfigFormField(自动校验)
42
+
43
+ ```python
44
+ from django_kv_config_widget.fields import KVConfigFormField
45
+
46
+
47
+ class ConfigForm(forms.Form):
48
+ settings = KVConfigFormField(
49
+ label="应用配置",
50
+ default_keys=[
51
+ {"key": "DB_URL", "type": "str", "default": "postgres://localhost:5432/app", "placeholder": "数据库连接地址"},
52
+ {"key": "DEBUG", "type": "bool", "default": "true"},
53
+ {"key": "PORT", "type": "int", "default": "8080"},
54
+ {"key": "ALLOWED_HOSTS", "type": "json", "placeholder": '["localhost", "example.com"]'},
55
+ "LOG_LEVEL",
56
+ ],
57
+ required_keys=["DB_URL"],
58
+ allow_custom=False,
59
+ help_text="仅允许预定义的键,DB_URL 为必填项。",
60
+ )
61
+ ```
62
+
63
+ ### 直接使用 Widget(手动校验)
64
+
65
+ ```python
66
+ from django import forms
67
+ from django_kv_config_widget.widgets import DjangoKVConfigWidget
68
+
69
+
70
+ class ConfigForm(forms.Form):
71
+ settings = forms.JSONField(
72
+ widget=DjangoKVConfigWidget(
73
+ default_keys=[
74
+ {"key": "DB_URL", "type": "str", "default": "postgres://localhost/app"},
75
+ {"key": "PORT", "type": "int", "default": "8080"},
76
+ ],
77
+ required_keys=["DB_URL"],
78
+ ),
79
+ )
80
+
81
+ def clean_settings(self):
82
+ value = self.cleaned_data.get("settings") or {}
83
+ widget = self.fields["settings"].widget
84
+ DjangoKVConfigWidget.validate_required_keys(value, widget.required_keys)
85
+ DjangoKVConfigWidget.validate_types(value, DjangoKVConfigWidget.get_type_map(widget.default_keys))
86
+ if not widget.allow_custom:
87
+ allowed = {dk["key"] for dk in widget.default_keys}
88
+ for k in value:
89
+ if k not in allowed:
90
+ raise forms.ValidationError("'%s' 不在预定义键中" % k)
91
+ return value
92
+ ```
93
+
94
+ ### 在 Django Admin 中使用
95
+
96
+ ```python
97
+ from django.contrib import admin
98
+ from django import forms
99
+ from django_kv_config_widget.fields import KVConfigFormField
100
+ from .models import MyModel
101
+
102
+
103
+ class MyModelForm(forms.ModelForm):
104
+ config = KVConfigFormField(
105
+ label="配置",
106
+ default_keys=[{"key": "DB_URL", "type": "str", "default": "postgres://localhost/app"}],
107
+ required_keys=["DB_URL"],
108
+ allow_custom=False,
109
+ )
110
+ class Meta:
111
+ model = MyModel
112
+ fields = "__all__"
113
+
114
+
115
+ @admin.register(MyModel)
116
+ class MyModelAdmin(admin.ModelAdmin):
117
+ form = MyModelForm
118
+ ```
119
+
120
+ ### `default_keys` 格式
121
+
122
+ | 形式 | 示例 | 效果 |
123
+ |------|------|------|
124
+ | `str` | `"LOG_LEVEL"` | 文本输入框,无默认值 |
125
+ | `dict` | `{"key":"PORT","type":"int","default":"8080","placeholder":"端口号"}` | 类型化输入,有默认值 + 占位提示 |
126
+
127
+ ### 类型对照
128
+
129
+ | 类型 | 控件 | 值 |
130
+ |------|------|-----|
131
+ | `str` | textarea(单行外观) | 任意文本,支持换行 |
132
+ | `text` | textarea | 任意文本,支持换行 |
133
+ | `number` | `<input type="number">` | 浮点数 |
134
+ | `int` | `<input type="number" step="1">` | 整数 |
135
+ | `bool` | `<select>` | 空 / `true` / `false`(三态) |
136
+ | `json` | textarea | 合法 YAML/JSON |
137
+
138
+ ### 导入 / 导出
139
+
140
+ 点击 **Import** 粘贴 YAML、JSON 或 `KEY=VALUE` 文本,自动解析填充表格。
141
+ 点击 **Export** 将当前数据导出为 YAML。
142
+
143
+ ### 国际化
144
+
145
+ 默认界面为英文。设置以下内容可切换为中文:
146
+
147
+ ```python
148
+ LANGUAGE_CODE = "zh-hans"
149
+ ```
150
+
151
+ 校验错误信息和占位提示均已翻译。
152
+
153
+ ## API
154
+
155
+ ### `DjangoKVConfigWidget`
156
+
157
+ | 参数 | 类型 | 默认值 | 说明 |
158
+ |------|------|--------|------|
159
+ | `default_keys` | `list[str \| dict]` | `[]` | 预定义键。`str` 为简单键名;`dict` 支持 `key`、`default`、`placeholder`、`type` |
160
+ | `required_keys` | `list[str]` | `[]` | 必填键列表,值为空时提交不通过 |
161
+ | `allow_custom` | `bool` | `True` | 是否允许自定义键。`False` 时键以下拉选择,不可重复,用完后隐藏添加按钮 |
162
+ | `attrs` | `dict` | `None` | 标准 Django widget HTML 属性 |
163
+
164
+ 静态方法:
165
+ - `validate_required_keys(value, required_keys)` — 必填键为空时抛出 `ValidationError`
166
+ - `validate_types(value, type_map)` — 类型不匹配时抛出 `ValidationError`
167
+ - `get_type_map(default_keys)` → `dict` — 构建 `{键名: 类型}` 映射
168
+
169
+ ### `KVConfigFormField(forms.JSONField)`
170
+
171
+ 自动校验必填键、类型、自定义键限制。接收 `default_keys`、`required_keys`、`allow_custom` 参数(透传给内部 widget)。
172
+
173
+ ## 数据格式
174
+
175
+ 提交时以 JSON 对象形式存储。空键被丢弃。空提交返回 `""`。
176
+
177
+ ```json
178
+ {"KEY1": "value1", "KEY2": "value2"}
179
+ ```
180
+
181
+ ## 开发
182
+
183
+ ```bash
184
+ git clone ...
185
+ cd django-kv-config-widget
186
+ pip install -r requirements.txt
187
+ python manage.py migrate
188
+ python manage.py createsuperuser
189
+ python manage.py runserver
190
+ ```
191
+
192
+ - 管理后台:http://127.0.0.1:8000/admin/
193
+
194
+ ## 更新记录
195
+
196
+ ### v0.1.0
197
+
198
+ 1. 动态添加/删除行,多行值自动撑高
199
+ 2. `default_keys` 支持 dict 格式(`default`、`placeholder`、`type`)
200
+ 3. 类型系统:`str`、`text`、`number`、`int`、`bool`(NullableBoolean)、`json`
201
+ 4. `required_keys` — 必填键为空时阻止提交
202
+ 5. `allow_custom` — 预定义键下拉选择;禁止重复;用完后隐藏添加按钮
203
+ 6. `KVConfigFormField` — 内置必填 + 类型 + 自定义键校验
204
+ 7. 导入/导出 — YAML、JSON、KEY=VALUE
205
+ 8. 键盘:Enter 键→值跳转;Enter 值内换行
206
+ 9. 国际化(英文默认,中文通过 `LANGUAGE_CODE=zh-hans`)
207
+ 10. 系统焦点环、Django CSS 变量主题
208
+ 11. `validate_required_keys`、`validate_types`、`get_type_map` 静态方法
@@ -0,0 +1,6 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class DjangoKvConfigWidgetConfig(AppConfig):
5
+ name = "django_kv_config_widget"
6
+ verbose_name = "KV Config Widget"
@@ -0,0 +1,33 @@
1
+ from django import forms
2
+
3
+ from .widgets import DjangoKVConfigWidget
4
+
5
+
6
+ class KVConfigFormField(forms.JSONField):
7
+
8
+ def __init__(self, *, default_keys=None, required_keys=None, allow_custom=True, **kwargs):
9
+ widget = kwargs.pop("widget", None) or DjangoKVConfigWidget(
10
+ default_keys=default_keys,
11
+ required_keys=required_keys,
12
+ allow_custom=allow_custom,
13
+ )
14
+ kwargs["widget"] = widget
15
+ super().__init__(**kwargs)
16
+
17
+ def clean(self, value):
18
+ value = super().clean(value)
19
+ required_keys = getattr(self.widget, "required_keys", None)
20
+ default_keys = getattr(self.widget, "default_keys", None) or []
21
+ allow_custom = getattr(self.widget, "allow_custom", True)
22
+ if required_keys and value:
23
+ DjangoKVConfigWidget.validate_required_keys(value, required_keys)
24
+ if value and isinstance(value, dict):
25
+ type_map = DjangoKVConfigWidget.get_type_map(default_keys)
26
+ if type_map:
27
+ DjangoKVConfigWidget.validate_types(value, type_map)
28
+ if not allow_custom:
29
+ allowed = {dk["key"] for dk in default_keys}
30
+ for k in value:
31
+ if k not in allowed:
32
+ raise forms.ValidationError("'%s' is not in the predefined keys" % k)
33
+ return value
@@ -0,0 +1,88 @@
1
+ # Chinese (Simplified) translations for django-kv-config-widget
2
+ # Copyright (C) 2025 rRR0VrFP
3
+ #
4
+ msgid ""
5
+ msgstr ""
6
+ "Project-Id-Version: django-kv-config-widget 0.1.0\n"
7
+ "Report-Msgid-Bugs-To: \n"
8
+ "POT-Creation-Date: 2025-07-25 10:00+0800\n"
9
+ "PO-Revision-Date: 2025-07-25 10:00+0800\n"
10
+ "Last-Translator: rRR0VrFP <rrr0vrfp@qq.com>\n"
11
+ "Language-Team: Chinese (Simplified)\n"
12
+ "Language: zh_Hans\n"
13
+ "MIME-Version: 1.0\n"
14
+ "Content-Type: text/plain; charset=UTF-8\n"
15
+ "Content-Transfer-Encoding: 8bit\n"
16
+ "Plural-Forms: nplurals=1; plural=0;\n"
17
+
18
+ # Template strings
19
+ msgid "Key"
20
+ msgstr "键"
21
+
22
+ msgid "Value"
23
+ msgstr "值"
24
+
25
+ msgid "Actions"
26
+ msgstr "操作"
27
+
28
+ msgid "Key name"
29
+ msgstr "键名"
30
+
31
+ msgid "Copy row"
32
+ msgstr "复制行"
33
+
34
+ msgid "Move up"
35
+ msgstr "上移"
36
+
37
+ msgid "Move down"
38
+ msgstr "下移"
39
+
40
+ msgid "Delete"
41
+ msgstr "删除"
42
+
43
+ msgid "+ Add"
44
+ msgstr "+ 添加"
45
+
46
+ msgid "Import"
47
+ msgstr "导入"
48
+
49
+ msgid "Export"
50
+ msgstr "导出"
51
+
52
+ msgid "Paste YAML / JSON / KEY=VALUE format text..."
53
+ msgstr "粘贴 YAML/JSON/KEY=VALUE 格式文本..."
54
+
55
+ msgid "Cancel"
56
+ msgstr "取消"
57
+
58
+ msgid "OK"
59
+ msgstr "确定"
60
+
61
+ msgid "Close"
62
+ msgstr "关闭"
63
+
64
+ # Validation messages
65
+ msgid "Required fields: %s"
66
+ msgstr "请填写必填项:%s"
67
+
68
+ msgid "'%s' must be %s, got '%s'"
69
+ msgstr "'%s' 需要 %s 类型,当前值 '%s' 无效"
70
+
71
+ # Type labels
72
+ msgid "string"
73
+ msgstr "字符串"
74
+
75
+ msgid "text"
76
+ msgstr "多行文本"
77
+
78
+ msgid "number"
79
+ msgstr "浮点数"
80
+
81
+ msgid "integer"
82
+ msgstr "整数"
83
+
84
+ msgid "boolean"
85
+ msgstr "布尔值"
86
+
87
+ msgid "JSON"
88
+ msgstr "JSON"
@@ -0,0 +1,50 @@
1
+ .django-kv-config-widget { max-width: 800px; border: 1px solid var(--border-color, #ccc); padding: 0; border-radius: 4px; }
2
+ .django-kv-config-widget:focus-within { outline: 2px solid -webkit-focus-ring-color; outline-offset: 1px; }
3
+ .django-kv-config-widget .django-kv-config-table { width: 100%; border-collapse: collapse; table-layout: fixed; }
4
+ .django-kv-config-widget .django-kv-config-table th { text-align: left; padding: 4px 8px; font-weight: 600; }
5
+
6
+ .django-kv-config-widget .django-kv-config-key-col { width: 30%; }
7
+ .django-kv-config-widget .django-kv-config-value-col { width: auto; }
8
+ .django-kv-config-widget .django-kv-config-action-col { width: 20px; }
9
+ .django-kv-config-widget .django-kv-config-table th.django-kv-config-action-col,
10
+ .django-kv-config-widget .django-kv-config-table td.django-kv-config-action-col { text-align: center; white-space: nowrap; overflow: hidden; }
11
+ .django-kv-config-widget .django-kv-config-key-input { width: 100%; box-sizing: border-box; }
12
+ .django-kv-config-widget .django-kv-config-value-input { width: 100%; box-sizing: border-box; padding: 4px 6px; }
13
+ .django-kv-config-widget .django-kv-config-value-input { resize: none; overflow: hidden; max-height: 10em; font-family: inherit; font-size: inherit; line-height: 1.4; }
14
+ .django-kv-config-widget .django-kv-config-value-input[type="number"] { height: 1.875rem; }
15
+ .django-kv-config-widget .django-kv-config-value-input[type="number"] { text-align: right; }
16
+
17
+ .django-kv-config-widget .django-kv-config-table td.django-kv-config-action-col { padding: 2px; vertical-align: middle; }
18
+
19
+ .django-kv-config-widget .django-kv-config-remove {
20
+ cursor: pointer; border: none; background: none; font-size: 14px; padding: 1px 2px; border-radius: 2px; line-height: 1; color: var(--error-fg, #ba2121); transition: background 0.15s; display: block; margin: 0 auto; width: 16px; text-align: center;
21
+ }
22
+ .django-kv-config-widget .django-kv-config-remove:hover { background: rgba(186,33,33,0.1); }
23
+
24
+ .django-kv-config-widget .django-kv-config-toolbar { padding: 4px 8px; display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
25
+ .django-kv-config-widget .django-kv-config-add,
26
+ .django-kv-config-widget .django-kv-config-import,
27
+ .django-kv-config-widget .django-kv-config-export {
28
+ cursor: pointer; border: 1px solid var(--border-color, #ccc); background: var(--darkened-bg, #f8f8f8);
29
+ padding: 4px 10px; border-radius: 3px; font-size: inherit;
30
+ }
31
+ .django-kv-config-widget .django-kv-config-add:hover,
32
+ .django-kv-config-widget .django-kv-config-import:hover,
33
+ .django-kv-config-widget .django-kv-config-export:hover { background: #e8e8e8; }
34
+
35
+ .django-kv-config-widget .django-kv-config-required-row .django-kv-config-key-input { background: var(--darkened-bg, #f8f8f8); cursor: default; }
36
+ .django-kv-config-widget .django-kv-config-required-row .django-kv-config-value-input { border-left: 2px solid var(--primary, #79aec8); }
37
+
38
+ .errors .django-kv-config-widget input,
39
+ .errors .django-kv-config-widget textarea,
40
+ .errors .django-kv-config-widget select { border-color: var(--border-color, #ccc); }
41
+ .errors .django-kv-config-widget .django-kv-config-required-row .django-kv-config-value-input[aria-required="true"]:placeholder-shown { border-color: var(--error-fg, #ba2121); }
42
+ .errors .django-kv-config-required-row .django-kv-config-value-input[aria-required="true"]:placeholder-shown { border-left-color: var(--error-fg, #ba2121); }
43
+
44
+ .django-kv-config-modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 9999; display: flex; align-items: center; justify-content: center; }
45
+ .django-kv-config-modal-backdrop { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.3); }
46
+ .django-kv-config-modal-dialog { position: relative; background: var(--body-bg, #fff); border: 1px solid var(--border-color, #ccc); border-radius: 4px; padding: 16px; min-width: 400px; max-width: 600px; width: 90%; box-shadow: 0 4px 12px rgba(0,0,0,0.15); }
47
+ .django-kv-config-modal-text { width: 100%; min-height: 200px; max-height: 400px; box-sizing: border-box; font-family: monospace; font-size: 13px; padding: 8px; border: 1px solid var(--border-color, #ccc); border-radius: 3px; }
48
+ .django-kv-config-modal-actions { text-align: right; margin-top: 8px; }
49
+ .django-kv-config-modal-actions button { margin-left: 6px; cursor: pointer; border: 1px solid var(--border-color, #ccc); background: var(--darkened-bg, #f8f8f8); padding: 4px 12px; border-radius: 3px; font-size: inherit; }
50
+ .django-kv-config-modal-actions button:hover { background: #e8e8e8; }