Множественный выбор в flask-admin form_choices

Ну, я недавно подошел к flbk-admin и не могу понять, как решить эту проблему. Я знаю, что я могу использовать form_choices ограничить возможные значения для текстового поля, указав список кортежей. Тем не мение, form_choices позволяет выбрать только одно значение за раз. Как я могу указать, что в некоторых случаях мне может понадобиться список значений через запятую?

Я попробовал этот обходной путь:

form_args = {
    'FIELD': {
        'render_kw': {"multiple": "multiple"},
    }
}

но, несмотря на то, что на веб-странице действительно отображается многоэлементный ввод, сохраняется только первое значение.

EIDT 13.05.2017

Немного поиграв с flask-admin, я нашел два возможных (частичных) решения для моего вопроса, оба с определенными недостатками.

1) Первый касается использования Select2TagsField

from flask_admin.form.fields import Select2TagsField
...
form_extra_fields = {
    'muri': Select2TagsField()
}

С помощью этого метода можно легко реализовать меню выбора для обычного ввода текста, хотя в настоящее время я не понимаю, как передать выбор в Select2TagsField. Он хорошо работает как своего рода многократный ввод свободного текста. Однако, насколько я понимаю, невозможно соединить Select2TagsField и form_choices

2) Второе немного длиннее, но предлагает больше контроля над кодом (по крайней мере, я предполагаю). Тем не менее это подразумевает использование form_choices, но на этот раз в паре с on_model_change

form_args = {
    'FIELD': {
        'render_kw': {"multiple": "multiple"},
    }
}
form_choices = {'FIELD': [
    ('1', 'M1'), ('2', 'M2'), ('3', 'M3'), ('4', 'M4')
]}
...
def on_model_change(self, form, model, is_created):
    if len(form.FIELD.raw_data) > 1:
        model.FIELD = ','.join(form.FIELD.raw_data)

Это решение, несмотря на предыдущее, позволяет отображать варианты и хорошо работает при добавлении данных в модель, но при редактировании оно создает некоторые проблемы. Каждый раз, когда я открываю диалог редактирования, ПОЛЕ будет пустым. Если я смотрю на данные, отправленные в форму (с on_form_prefill печатью form.FIELD.data) Я получаю строку через запятую в терминале, но в соответствующем поле выбора на веб-странице ничего не отображается.

2 ответа

Может быть, это уже устарело, но мне удалось изменить его и заставить работать с полем с множественным выбором из postgres. Чтобы это работало, я расширил Select2Field, чтобы знать, как работать со списком:

class MultipleSelect2Field(Select2Field):
    """Extends select2 field to make it work with postgresql arrays and using choices.

    It is far from perfect and it should be tweaked it a bit more.
    """

    def iter_choices(self):
        """Iterate over choices especially to check if one of the values is selected."""
        if self.allow_blank:
            yield (u'__None', self.blank_text, self.data is None)

        for value, label in self.choices:
            yield (value, label, self.coerce(value) in self.data)

    def process_data(self, value):
        """This is called when you create the form with existing data."""
        if value is None:
            self.data = []
        else:
            try:
                self.data = [self.coerce(value) for value in value]
            except (ValueError, TypeError):
                self.data = []

    def process_formdata(self, valuelist):
        """Process posted data."""
        if not valuelist:
            return

        if valuelist[0] == '__None':
            self.data = []
        else:
            try:
                self.data = [self.coerce(value) for value in valuelist]
            except ValueError:
                raise ValueError(self.gettext(u'Invalid Choice: could not coerce'))

    def pre_validate(self, form):
        """Validate sent keys to make sure user don't post data that is not a valid choice."""
        sent_data = set(self.data)
        valid_data = {k for k, _ in self.choices}
        invalid_keys = sent_data - valid_data
        if invalid_keys:
            raise ValueError('These values are invalid {}'.format(','.join(invalid_keys)))

и использовать это сделать это на ModelView

class SomeView(ModelView):
    form_args = dict(FIELD=dict(render_kw=dict(multiple="multiple"), choices=CHOICES_TUPLE))
    form_overrides = dict(FIELD=MultipleSelect2Field)

Чтобы этот подход работал, вам нужно использовать столбец, в котором можно хранить список элементов. По крайней мере с sqlite это невозможно с помощью Flask-Admin. Однако было бы лучше, чтобы вы сохраняли свой выбор в отдельной модели данных и использовали ограничения, чтобы связать две модели. Смотрите рабочий пример здесь.

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView


app = Flask(__name__)
app.config['SECRET_KEY'] = '8e12c91677b3b3df266a770b22c82f2f'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
db = SQLAlchemy(app)
admin = Admin(app)


item_tag_relation = db.Table('item_tag_relation',
    db.Column('item_id', db.Integer, db.ForeignKey('item.id')),
    db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'))
)


class Item(db.Model):
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String())
    tags = db.relationship("Tag",
                               secondary=item_tag_relation,
                               backref="items")

    def __repr__(self):
        return self.name


class Tag(db.Model):
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String())

    def __repr__(self):
        return self.name


class ItemModelView(ModelView):
    pass


db.create_all()
admin.add_view(ItemModelView(Item, db.session))
admin.add_view(ModelView(Tag, db.session))

if __name__ == '__main__':
    app.run(debug=True)
Другие вопросы по тегам