如何动态渲染flask admin中的字段

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

如何管理所选模块类型上的可显示字段?

我的目标是:当选择模块类型“帖子”时,我想显示“标题”和“描述”字段。当我选择不同的类型时 - 我只想渲染“标题”字段

我应该使用什么表单方法来实现该行为? ModelView

class DailyRoutineTaskPackageView(ModelView):
    can_create = True
    can_edit = True
    can_delete = True
    page_size = 20

    create_template = "daily_routine_task_create.html"
    edit_template = "daily_routine_task_edit.html"

    form_columns = (
        "name",
        "active",
        "community",
        "datetime_start",
        "datetime_end",
    )

    form_ajax_refs = {
        "lgf_uuid": lifegraph_function_loader,
    }
    form_args = dict(
        community=dict(validators=[DataRequired()]),
    )

    inline_models = (DailyRoutineTaskInlineView(DailyRoutineTask),)
class DailyRoutineTaskInlineView(InlineFormAdmin):
    form_columns = ("id", "sequence_number", "routine_type", "routine_id", "custom_title", "custom_description", "lgf_uuid")

    # TODO: validate sequence

    form_overrides = dict(
        routine_type=SelectField,
        routine_id=NonValidatingSelectField,
        lgf_uuid=CompassAjaxSelectField,
        custom_title=TextField,
        custom_description=TextField,
    )

    form_extra_fields = {
        "sequence_number": IntegerField("Sequence Number", [validators.DataRequired()]),
        "lgf_uuid": lifegraph_function_loader.field_type(
            loader=lifegraph_function_loader,
            label="LifeGraph Function",
            allow_blank=True,
        ),
        "custom_title": TextField("Title"),
    }
    # based on some conditions
    form_extra_fields["custom_description"] = TextField("Description")
flask-wtforms flask-admin
3个回答
0
投票

通过操作 HTML 模板在前端使用这个丑陋的东西来实现它

  function getSelectedTasksArray() {
    // form array like ['service', 'article', 'post']
    return $('select[id$=-routine_type]').map(function () {
      return this.value
    }).get();
  };

  function updateAllTasks(selected_tasks_array) {
    selected_tasks_array.forEach(function (item, index) {
      let input_id = "input[id=tasks-".concat(index, "-custom_description]");
      if (item != "service") {
        $(input_id).val(null);
        $(input_id).parent().parent().parent().hide();  // hide description fields 
      } else {
        $(input_id).parent().parent().parent().show();  // show description fields 
      }
    });
  }

0
投票

我找到了一个解决方案,但它有一个技巧,可以立即从创建页面重定向到编辑页面。如果不使用大量 JS 编写新的小部件,就无法实现这样的目标。但是,可以制作类似的东西。

我的任务如下:创建一个名为“模型”的内联模型,其中包含“类型”和“值”列。根据“类型”,“值”可以是字符串输入、整数输入或选择框。

表单的主要问题是,当 Python 初始化代码时,在函数外部声明的所有内容都会被构建和缓存。您无法即时更改表单架构和字段类型;您只能创建一个新的。这是根据 WTForms 官方文档:

class CharacteristicEnumMultipleValueField(QuerySelectMultipleField):
    def __init__(self, *args, characteristic_id: int = None, **kwargs):
        super(CharacteristicEnumMultipleValueField, self).__init__(*args, **kwargs)
        self.characteristic_id = characteristic_id
        self.query_factory = self._query_factory

    def populate_obj(self, obj, name):
        # store data from form into db
        setattr(obj, name, ','.join([str(i.id) for i in self.data]))

    def process_data(self, value):
        # load data from db to render
        if not value:
            return []

        selected_ids = [int(i) for i in value.split(',')]
        self.data = db_session\
            .query(CharacteristicTypeItem)\
            .filter(CharacteristicTypeItem.id.in_(selected_ids))\
            .all()

    def iter_choices(self):
        self.process_data(self.object_data)
        return super().iter_choices()

    def _query_factory(self):
        return db_session.query(CharacteristicTypeItem).filter(CharacteristicTypeItem.characteristic_id == self.characteristic_id)

    def _get_object_list(self):
        data = super(CharacteristicEnumMultipleValueField, self)._get_object_list()
        return data


class CharacteristicEnumMultipleValueCheckboxField(CharacteristicEnumMultipleValueField):
    widget = CheckboxListInput()


对于像 SelectBox 这样的字段,我们需要创建一个自定义元素,因为我们将数据存储在数据库中的常规字符串字段中,但 SelectBox 操作对象列表。


class MyCustomModelView(ModelView):
    ....

    def edit_form(self, obj=None):
        form = super().edit_form(obj=obj)

        for index, char_var in enumerate(obj.characteristics_values):
            dynamic_field = StringField(label='Value')

            if char_var.characteristic.type == 'number':
                dynamic_field = IntegerField(label='Value')

            elif char_var.characteristic.type in ('items_and', 'items_or'):
                dynamic_field = CharacteristicEnumMultipleValueField(
                    label='Value',
                    characteristic_id=copy.copy(char_var.characteristic_id)
                )
            elif char_var.characteristic.type == 'checkbox':
                options = (
                    db_session
                    .query(CharacteristicTypeItem)
                    .filter(CharacteristicTypeItem.characteristic_id == char_var.characteristic_id)
                )

                dynamic_field = CharacteristicEnumMultipleValueCheckboxField(
                    query_factory=lambda: options,
                    label='Значение',
                    characteristic_id=copy.copy(char_var.characteristic_id)
                )

            new_inline_form_class = type(
                'DynamicInlineModel',
                (form.characteristics_values.form, ),
                {'value': dynamic_field}
            )

            form.characteristics_values.entries[index].form = new_inline_form_class(
                formdata=request.form,
                obj=obj.characteristics_values[index],
                prefix=form.characteristics_values.entries[index].id
            )
            form.characteristics_values.entries[index].form.process(
                request.form,
                obj.characteristics_values[index]
            )

        return form

主要的魔力发生在 edit_form 中。我们需要动态创建要呈现的表单。我决定从实际构建的表单继承它,并通过将其传递给类型函数来仅修改所需的字段。之后,我们需要将表单正确放置在存储内联模型对象的字段表单中。 Flask-Admin 以不同的方式处理 inline_models,而不是作为一个大表单(这就是为什么我们不为整个 ModelView 构建表单,而只为一个相关字段构建表单)。

然后,需要一个技巧:表单字段列表在创建和编辑之间应该有所不同。在创建视图中,我们应该预填充数据库模型并设置所需的字段类型。按“保存”后,我们应该创建此模型并重定向到适当的编辑页面。对于用户来说,感觉就像一个向导,但没有提示。

from flask_admin.form import rules


class MyCustomModelView(ModelView):
    ....

    form_create_rules = [
        rules.FieldSet(['categories', 'name']),
    ]
    form_edit_rules = [
        rules.Field('name'),
        CustomizableFieldRule('categories', field_args={'disabled': True}),
        rules.FieldSet(['collected_products', 'characteristics_values', 'prices']),
    ]

这就是您可以在常规模型的 create_view 中隐藏某些字段的方法。


class CharacteristicValueInlineModel(InlineFormAdmin):
    ....
    form_widget_args = {
        'characteristic': {
            'disabled': True
        }
    }

这是在内联模型的 edit_view 中隐藏字段的示例。对于创作来说,你可以通过创建动态表单来做任何你想做的事情。

from werkzeug.datastructures.structures import MultiDict, ImmutableMultiDict

class MyCustomModelView(ModelView):
    @expose('/new/', methods=('GET', 'POST'))
    def create_view(self):
        if request.method.lower() != 'post':
            return super(BaseProductModelView, self).create_view()

        updated_form = MultiDict(request.form)
        updated_form.add('_continue_editing', 1)
        request.form = ImmutableMultiDict(updated_form)

        return super(BaseProductModelView, self).create_view()

这是创建模型后将用户重定向到编辑页面的技巧。

就是这样。您需要做的就是在 edit_form 中创建一个新的内联表单,填充它,然后重定向到编辑页面。它不像创建新的模型视图那么简单,但是通过重定向,它比编写大量 JavaScript 代码来操作表单并使用 AJAX 加载新部件要简单得多。

这样,您只需在 edit_form 中创建一个新表单并返回它,就可以为常规 ModelView 创建动态表单。


class MyModelView(ModelView):
    ...
    def edit_form(self, ojb = None):
        new_fields = {'name': StringField(), 'value': IntegerValue()}
        new_form = type('MyDynamicForm', (Form, ), new_fields)
        form = new_form(obj=obj)
        return form

希望,您发现这个解决方案很有帮助,并且多年后出现了一些带有纯Python实现的公共解决方案。


-1
投票

您可以尝试将js与edit_template和jinja2一起使用。

© www.soinside.com 2019 - 2024. All rights reserved.