我已经创建了一个模型,相应的字段模型,并打算重用内置的
CheckboxSelectMultiple
小部件以在 Wagtail 管理中使用。这个概念是一个保存为位字段的多选权限字段:
# Model class
class Perm(IntFlag):
Empty = 0
Read = 1
Write = 2
我使用 Django 的 model field's 文档创建了一个字段模型,可以将我的
Perm
类型转换为数据库或从数据库转换(保存为按位或表示相应权限位的整数字段):
# Model field class
class PermField(models.Field):
description = "Permission field"
def __init__(self, value=Perm.Empty.value, *args, **kwargs):
self.value = value
kwargs["default"] = Perm.Empty.value
super().__init__(*args, **kwargs)
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
args += [self.value]
return name, path, args, kwargs
def db_type(self, connection):
return "bigint" # PostgresSQL
def from_db_value(self, value, expression, connection):
if value is None:
return Perm.Empty
return Perm(value)
def to_python(self, value):
if isinstance(value, Perm):
return value
if isinstance(value, str):
return self.parse(value)
if value is None:
return value
return Perm(value)
def parse(self, value):
v = Perm.Empty
if not isinstance(ast.literal_eval(value), list):
raise ValueError("%s cannot be converted to %s", value, type(Perm))
for n in ast.literal_eval(value):
v = v | Perm(int(n))
return v
然后,我还创建了一个 Wagtail 片段来使用这个新字段和类型:
perm_choices = [
(Perm.Read.value, Perm.Read.name),
(Perm.Write.value, Perm.Write.name)
]
@register_snippet
class Permission(models.Model):
name = models.CharField(max_length=32, default="None")
perm = PermField()
panels = [FieldPanel("perm", widget=forms.CheckboxSelectMultiple(choices=perm_choices))]
创建新片段效果很好,但编辑现有片段只会显示一个空的
CheckboxSelectMultiple
小部件:
我显然需要在初始化时填充表单。理想情况下,使用内置的
CheckboxSelectMultiple
小部件。为此,我尝试定义以下形式:
@register_snippet
class Permission(models.Model):
# ...
# Custom form subclass for snippets per documentation
# https://docs.wagtail.org/en/v2.15/advanced_topics/customisation/page_editing_interface.html
class Permission(WagtailAdminModelForm):
p = forms.IntegerField(
widget=forms.CheckboxSelectMultiple(
choices=perm_choices,
),
label="Permission field",
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['p'].initial = {
k.name for k in [Perm.Read, Perm.Write] if k.value in Perm(int(p))
}
def clean_selected_permissions(self):
selected_p = self.cleaned_data["p"]
value = Perm.Empty
for k in selected_p:
value |= Perm.__members__[k]
return value
class Meta:
model=Permission
fields=["perm"]
# Models not defined yet error here!
Permission.base_form_class = PermissionForm
但是,我无法使该表格发挥作用。有一个循环,其中
PermissionForm
需要定义 Permission
,反之亦然。使用gasman 的全局模型表单分配(如此处所示)对我不起作用。我还想知道是否有一种更简单的方法来解决我所面临的但我没有看到的问题。
ModelForm
,它将 CheckBoxSelectMultiple
直接链接到 models.ManyToManyField
。这是有效的,因为 ManyToManyField
类型自动与小部件兼容。就我而言,我必须自己设置。MultiSubscriptionForm
,它本身包含一个用于填充现有字段的关键字参数。这在我的情况下不存在。CheckboxSelectMultiple
模板,以显示特殊的排列。答案提供了 HTML 模板来执行此操作,但在其他方面依赖于自动链接/填充复选框的 ManyToMany
字段类型/关系。好吧,我明白了。总之,您必须创建以下内容:
PermissionField
类型)。CheckboxSelectMultiple
get_context
方法以添加“已选中”状态。这与问题基本没有变化
class PermField(models.Field):
description = "Permission field"
def __init__(self, value=Perm.Empty.value, *args, **kwargs):
self.value = value
kwargs["default"] = Perm.Empty.value
super().__init__(*args, **kwargs)
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
args += [self.value]
return name, path, args, kwargs
def db_type(self, connection):
return "bigint" # PostgresSQL
def from_db_value(self, value, expression, connection):
if value is None:
return Perm.Empty
return Perm(value)
def to_python(self, value):
if isinstance(value, Perm):
return value
if isinstance(value, str):
return self.parse(value)
if value is None:
return value
return Perm(value)
def parse(self, value):
v = Perm.Empty
if isinstance(ast.literal_eval(value), int):
return Perm(ast.literal_eval(value))
if not isinstance(ast.literal_eval(value), list):
raise ValueError("%s cannot be converted to %s", value, type(Perm))
# Note: The form will use a list: "['1','2',...]" that you need to
#. convert back into an actual value of your field type.
for n in ast.literal_eval(value):
v = v | Perm(int(n))
return v
请注意
parse
方法,因为保存表单时,将使用包含所选选项的字符串编码列表来调用字段类型的 to_python
方法。因此,您需要 (1) 检测并 (2) 将其转换回您的字段类型的值。
模型最终非常简单。只需指定您的自定义表单(请参阅下一节)
@register_snippet
class Permission(models.Model):
perm = PermField()
panels = [
FieldPanel("perm", widget=PermissionForm(choices=perm_choices))
]
我只是对
CheckboxSelectMultiple
进行了子类化,因为这样你就可以得到内置的 choices
关键字参数等。
class PermissionForm(forms.CheckboxSelectMultiple):
template_name = "permission_form.html"
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
# Set the "selected" flag here however you need for your type.
for option in context["widget"]["optgroups"]:
option[1][0]["selected"] = Perm(option[1][0]["value"]) in Perm(value)
return context
然后您可以覆盖
get_context
(在文档中这里进行了描述,尽管不是很好)并为模板提供您自己的上下文。我所做的是使用 pdb
自己检查上下文。这会产生一个字典结构,如下所示:
"widget": {
"name": "perm",
"is_hidden": False,
"required": True,
"value": ["0"],
"attrs": {"id": "id_perm"},
"template_name": "permission_form.html",
"optgroups": [
(
None,
[
{
"name": "perm",
"value": 1,
"label": "Read",
"selected": False,
"index": "0",
"attrs": {"id": "id_perm_0"},
"type": "checkbox",
"template_name": "django/forms/widgets/checkbox_option.html",
"wrap_label": True,
}
],
0,
),
(
None,
[
{
"name": "perm",
"value": 2,
"label": "Write",
"selected": False,
"index": "1",
"attrs": {"id": "id_perm_1"},
"type": "checkbox",
"template_name": "django/forms/widgets/checkbox_option.html",
"wrap_label": True,
}
],
1,
),
],
}
您可以看到我正在使用 selected
的
value
参数覆盖列表中每个选项元组的
get_context
布尔字段(这应该是创建表单时字段的值)。我希望这足以在使用默认模板创建表单时检查复选框,但这不起作用。因此,我
CheckboxMultipleSelect
小部件生成的 HTML 并对其进行了一些编辑以使用我提供的上下文:
<!--mysite/templates/permission_form.html-->
<div>
{% for option in widget.optgroups %}
<label for="{{widget.attrs.id}}_{{option.2}}">
<input type="checkbox"
name="{{widget.name}}"
value="{{option.1.0.value}}"
id="{{widget.attrs.id}}_{{option.2}}"
{% if option.1.0.selected %}
checked
{% endif %}
>
{{option.1.0.label}}
</label>
{% endfor %}
</div>
为了使用您自己的 Widget 模板而不发生错误,您需要首先对您的设置进行一些调整(感谢 https://stackoverflow.com/a/46208414/1883304 解决了这个问题):
# In wagtail, it should be something like mysite/settings.py
INSTALLED_APPS = [
...
"django.forms",
...
]
FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'