在 Wagtail 管理中使用我自己的模型填充 CheckboxSelectMultiple 小部件

问题描述 投票:0回答:1

背景

我已经创建了一个模型,相应的字段模型,并打算重用内置的

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
小部件:

Empty Wagtail CheckboxSelectMultiple upon editing an existing snippet


解决方案尝试

我显然需要在初始化时填充表单。理想情况下,使用内置的

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 的全局模型表单分配(如此处所示)对我不起作用。我还想知道是否有一种更简单的方法来解决我所面临的但我没有看到的问题。


类似的问题没有解决我的问题

python django wagtail wagtail-admin
1个回答
0
投票

好吧,我明白了。总之,您必须创建以下内容:

  1. 字段类型:创建您的字段类(例如,像我在问题中的
    PermissionField
    类型)。
  2. 模型:创建普通的 Django/Wagtail 模型(使用您的自定义字段和表单类)
  3. Form:创建模型表单,最好是从 Wagtail 或 Django 中继承一些东西,例如
    CheckboxSelectMultiple
    • 重写表单的
      get_context
      方法以添加“已选中”状态。
  4. 模板:使用自定义模板能够设置复选框状态。

字段类型

这与问题基本没有变化

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'
    
© www.soinside.com 2019 - 2024. All rights reserved.