Flask/Flask-Admin中Select2字段的实现
我正在尝试在我的Flask视图中实现Select2字段。简单来说,我想在Flask应用的视图中使用和Flask-admin模型创建视图中一样的Select2字段。目前,我的解决方案是使用来自wtforms的QuerySelectField
,它的样子大致是这样的:
class TestForm(Form):
name= QuerySelectField(query_factory=lambda: models.User.query.all())
这个方法让我可以加载和选择我需要的所有数据,但它没有提供Select2的搜索框等功能。目前我找到的只有来自flask/admin/form/fields和flask/admin/form/widgets的Select2Field
和Select2Widget
,和这篇帖子中提到的类似 https://stackoverflow.com/questions/24644960/how-to-steal-flask-admin-tag-form-field,还有Select2的文档在 http://ivaynberg.github.io/select2/。我理解这些可以重复使用,这意味着不需要其他自定义的小部件或字段。
如果有人能提供更多关于在Flask应用中实现Select2字段的信息(包括视图、模板、表单文件,以及如何正确“连接”所需的js和css文件,另外还包括如何用我需要的数据库模型加载这个字段),我将非常感激。
4 个回答
其他的回答好像不再有效了。我做的让它能工作的步骤是:
在后台:
from wtforms.ext.sqlalchemy.fields import QuerySelectField, QuerySelectMultipleField
...
users = users = QuerySelectMultipleField('users', query_factory=lambda: User.query.order_by(User.fullname).all(),
get_label=lambda u: u.fullname, get_pk=lambda u: u.id)
然后在前台:
$("#users").attr("multiple", "multiple").select2();
你需要手动添加 select2 的 CSS 和 JS 文件。
我最近在一个Flask应用的前端实现了一个“标签”字段,使用了Select2和WTForms。我写了一个示例应用,展示了我是如何让它工作的(主要的工作是在填充选择选项的视图代码和动态保存新选项的地方):
https://github.com/Jaza/flasktaggingtest
你可以在这里看到这个应用的演示:
https://flasktaggingtest.herokuapp.com/
我的示例中没有使用AJAX自动补全(它只是初始化表单时将所有可用的标签填充到选择字段中)。除此之外,应该包含了你在Flask视图/模板中一般想要的标签小部件的所有功能。
我有类似的需求,所以做了一个简单的示例。
请注意以下几点:
类 TestView
定义了三个路由:一个是获取视图,一个是提交视图,还有一个是Ajax查找视图。
函数 get_loader_by_name
接收一个字符串名称,并返回一个 QueryAjaxModelLoader
。这个函数在Ajax查找调用和 TestForm
字段定义中都有使用。
在Select2小部件中显示的文本是由用户模型的 __unicode__
方法返回的值。
我使用了 Faker 来生成示例用户数据。
文件 app.py:
from flask import Flask, render_template, url_for, request, abort, json, Response
from flask.ext.admin import Admin, BaseView, expose, babel
from flask.ext.admin.contrib.sqla.ajax import QueryAjaxModelLoader
from flask.ext.admin.model.fields import AjaxSelectField, AjaxSelectMultipleField
from flask.ext.sqlalchemy import SQLAlchemy
from wtforms import Form
from faker import Factory
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SECRET_KEY'] = 'super-secret'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)
try:
from flask_debugtoolbar import DebugToolbarExtension
DebugToolbarExtension(app)
except:
pass
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.Unicode(length=255), nullable=False)
last_name = db.Column(db.Unicode(length=255), nullable=False)
email = db.Column(db.Unicode(length=254), nullable=False, unique=True)
def __unicode__(self):
return u"{first} {last}; {email}".format(first=self.first_name, last=self.last_name, email=self.email)
def get_loader_by_name(name):
_dicts = {
'user': QueryAjaxModelLoader(
'user',
db.session, User,
fields=['first_name', 'last_name', 'email'],
page_size=10,
placeholder="Select a user"
)
}
return _dicts.get(name, None)
class TestView(BaseView):
def __init__(self, name=None, category=None,
endpoint=None, url=None,
template='admin/index.html',
menu_class_name=None,
menu_icon_type=None,
menu_icon_value=None):
super(TestView, self).__init__(name or babel.lazy_gettext('Home'),
category,
endpoint or 'admin',
url or '/admin',
'static',
menu_class_name=menu_class_name,
menu_icon_type=menu_icon_type,
menu_icon_value=menu_icon_value)
self._template = template
@expose('/', methods=('GET',))
def index_view(self):
_form = TestForm()
return self.render(self._template, form=_form)
@expose('/', methods=('POST',))
def post_view(self):
pass
@expose('/ajax/lookup/')
def ajax_lookup(self):
name = request.args.get('name')
query = request.args.get('query')
offset = request.args.get('offset', type=int)
limit = request.args.get('limit', 10, type=int)
loader = get_loader_by_name(name)
if not loader:
abort(404)
data = [loader.format(m) for m in loader.get_list(query, offset, limit)]
return Response(json.dumps(data), mimetype='application/json')
# Create admin and Test View
admin = Admin(app, name='Admin', template_mode='bootstrap3')
admin.add_view(TestView(template='test.html', name="Test", url='/test', endpoint='test'))
class TestForm(Form):
single_user = AjaxSelectField(
loader=get_loader_by_name('user')
)
multiple_users = AjaxSelectMultipleField(
loader=get_loader_by_name('user')
)
@app.route('/')
def index():
return render_template("index.html", link=url_for('test.index_view'))
def build_db():
db.drop_all()
db.create_all()
fake = Factory.create()
for index in range(0, 1000):
_first_name = fake.first_name()
_last_name = fake.last_name()
_user_db = User(
first_name=_first_name,
last_name=_last_name,
email="{first}.{last}{index}@example.com".format(first=_first_name.lower(), last=_last_name.lower(), index=index)
)
db.session.add(_user_db)
db.session.commit()
@app.before_first_request
def before_first_request():
build_db()
if __name__ == '__main__':
app.debug = True
app.run(port=5000, debug=True)
文件 templates/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test Select2</title>
</head>
<body>
<a href="{{ link }}">Go to the test form</a>
</body>
</html>
文件 templates/test.html:
{% import 'admin/static.html' as admin_static with context %}
{% import 'admin/lib.html' as lib with context %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test Select2</title>
<link href="{{ admin_static.url(filename='bootstrap/bootstrap3/css/bootstrap.min.css') }}" rel="stylesheet">
<link href="{{ admin_static.url(filename='bootstrap/bootstrap3/css/bootstrap-theme.min.css') }}" rel="stylesheet">
<link href="{{ admin_static.url(filename='admin/css/bootstrap3/admin.css') }}" rel="stylesheet">
{{ lib.form_css() }}
</head>
<body>
<div class="container">
<div class="row">
<div class="col-sm-10 col-sm-offset-2">
<form>
{{ lib.render_form_fields(form) }}
</form>
</div>
</div>
</div>
<script src="{{ admin_static.url(filename='vendor/jquery-2.1.1.min.js') }}" type="text/javascript"></script>
<script src="{{ admin_static.url(filename='bootstrap/bootstrap3/js/bootstrap.min.js') }}" type="text/javascript"></script>
<script src="{{ admin_static.url(filename='vendor/moment-2.8.4.min.js') }}" type="text/javascript"></script>
<script src="{{ admin_static.url(filename='vendor/select2/select2.min.js') }}" type="text/javascript"></script>
{{ lib.form_js() }}
</body>
</html>
更新于2018年7月
添加了一个独立的Flask扩展,已经在Github上发布 - Flask-Select2 - 仍在开发中。
这个对我有效:
...
from wtforms.ext.sqlalchemy.fields import QuerySelectField
from flask_admin.form.widgets import Select2Widget
...
class TestForm(Form):
name= QuerySelectField(query_factory=lambda: models.User.query.all(),
widget=Select2Widget())
在你的模板里:
{% extends "admin/master.html" %}
{% import 'admin/lib.html' as lib with context %}
{% block head %}
{{ super() }}
{{ lib.form_css() }}
{% endblock %}
{% block body %}
...
{% endblock %}
{% block tail %}
{{ super() }}
{{ lib.form_js() }}
{% endblock %}
如果需要的话,我可以尝试整理一个简单的示例。