如何管理所选模块类型上的可显示字段?
我的目标是:当选择模块类型“帖子”时,我想显示“标题”和“描述”字段。当我选择不同的类型时 - 我只想渲染“标题”字段
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")
通过操作 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
}
});
}
我找到了一个解决方案,但它有一个技巧,可以立即从创建页面重定向到编辑页面。如果不使用大量 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实现的公共解决方案。
您可以尝试将js与edit_template和jinja2一起使用。