【问题标题】:Correct way to add dynamic form fields to WagtailModelAdminForm将动态表单字段添加到 WagtailModelAdminForm 的正确方法
【发布时间】:2023-01-31 18:04:34
【问题描述】:

我有一个用例,我需要将动态表单字段添加到 WagtailModelAdminForm。使用标准的 Django,我通常只创建一个自定义子类并在表单的 __init__ 方法中添加字段。在 Wagtail 中,因为表单是用 edit_handlers 构建的,所以这变成了一个噩梦。

我有以下动态形式:

class ProductForm(WagtailAdminModelForm):
    class Meta:
        model = get_product_model()
        exclude = ['attributes', 'state', 'variant_of']

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self.instance:
            self.inject_attribute_fields()

    def inject_attribute_fields(self):
        for k, attr in self.instance.attributes.items():
            field_klass = None
            field_data = attr.get("input")
            field_args = {
                'label': field_data['name'],
                'help_text': field_data['help_text'],
                'required': field_data['is_required'],
                'initial': attr['value'],
            }

            if 'choices' in field_data:
                field_args['choices'] = (
                    (choice["id"], choice["value"])
                    for choice in field_data['choices']
                )

                if field_data['is_multi_choice']:
                    field_klass = forms.MultipleChoiceField
                else:
                    field_klass = forms.ChoiceField
            else:
                typ = field_data['attr_type']
                if typ == 'text':
                    field_klass = forms.CharField

                elif typ == 'textarea':
                    field_klass = forms.CharField
                    field_args['widget'] = forms.Textarea

                elif typ == 'bool':
                    field_klass = forms.BooleanField

                elif typ == 'int':
                    field_klass = forms.IntegerField

                elif typ == 'decimal':
                    field_klass = forms.DecimalField

                elif typ == 'date':
                    field_klass = forms.DateField
                    field_args['widget'] = AdminDateInput

                elif typ == 'time':
                    field_klass = forms.TimeField
                    field_args['widget'] = AdminTimeInput

                elif typ == 'datetime':
                    field_klass = forms.DateTimeField
                    field_args['widget'] = AdminDateTimeInput

            if field_klass is None:
                raise AttributeError('Cannot create widgets for invalid field types.')

            # Create the custom key
            self.fields[f"attributes__{k}"] = field_klass(**field_args)

接下来我自定义了 ModelAdmin EditView(创建视图中不存在属性):

class EditProductView(EditView):
    def get_edit_handler(self):
        summary_panels = [
            FieldPanel('title'),
            FieldPanel('description'),
            FieldPanel('body'),
        ]

        # NOTE: Product attributes are dynamic, so we generate them
        attributes_panel = get_product_attributes_panel(self.instance)

        variants_panel = []
        if self.instance.is_variant:
            variants_panel.append(
                InlinePanel(
                    'stockrecords',
                    classname="collapsed",
                    heading="Variants & Prices"
                )
            )
        else:
            variants_panel.append(ProductVariantsPanel())

          return TabbedInterface([
            ObjectList(summary_panels, heading='Summary'),
            
            # This panel creates dynamic panels related to the dynamic form fields,
            # but raises an error saying that the "fields are missing".
            # Understandable because it's not present on the original model
            # ObjectList(attributes_panel, heading='Attributes'),

            ObjectList(variants_panel, heading='Variants'),
            ObjectList(promote_panels, heading='Promote'),
            ObjectList(settings_panels, heading='Settings'),
        ], base_form_class=ProductForm).bind_to_model(self.model_admin.model)

这里是get_product_attributes_panel()函数供参考:

def get_product_attributes_panel(product) -> list:
    panels = []
    for key, attr in product.attributes.items():
        widget = None
        field_name = "attributes__" + key
        attr_type = attr['input'].get('attr_type')

        if attr_type == 'date':
            widget = AdminDateInput()

        elif attr_type == 'datetime':
            widget = AdminDateTimeInput()

        else:
            if attr_type is None and 'choices' in attr['input']:
                if attr['input']['is_multi_choice']:
                    widget = forms.SelectMultiple
                else:
                    widget = forms.Select
            else:
                widget = forms.TextInput()

        if widget:
            panels.append(FieldPanel(field_name, widget=widget))
        else:
            panels.append(FieldPanel(field_name))

    return panels

所以问题是...

A) 按照我上面的方式添加 ProductForm(通过将其用作 TabbedInterface 中的 base_form_class)几乎可以工作;它将字段添加到表单中;但是我无法控制渲染。

B) 如果我取消评论ObjectList(attributes_panel, heading='Attributes'), 行(为了更好地呈现字段),然后我的动态字段出现错误,说它们丢失了。

这是我正在从事的项目中非常重要的要求。

一个临时的解决方法是创建一个自定义面板直接在 html 模板中呈现动态字段;但是后来我失去了 Django 表单验证,这也是一个重要的要求。

有没有办法在 WagtailModelAdminForm 中添加动态字段,那果酱modeladmin 功能,如表单集、权限等。

【问题讨论】:

    标签: python django wagtail wagtail-admin


    【解决方案1】:

    我最终为属性创建了一个单独的AttributeForm

    然后,自定义面板将此新表单实例作为主表单的属性进行查找。作为主要形式的一个实例,我可以在调用主要形式 clean() 时“清理”这个内部形式,并在两种形式上提出我需要的任何错误。

    然后,我自定义了 EditView.post() 方法,以确保将 AttributesForm 的实例添加到我们的主要模型表单中。

    这是一种变通方法,但目前效果还不错。我希望有更简单的方法,但现在看起来不像。

    【讨论】:

      猜你喜欢
      • 2015-02-14
      • 2017-10-07
      • 2011-11-08
      • 1970-01-01
      • 2011-10-05
      • 2011-09-02
      • 1970-01-01
      • 1970-01-01
      • 2016-08-26
      相关资源
      最近更新 更多