我的flask-admin应用程序中有三个相关的SQLAlchemy模型(简化):
class Client(db.Model, BasicMixin, ActiveMixin, TimestampMixin):
id = db.Column(UUIDType, primary_key=True, default=uuid.uuid4)
title = db.Column(db.String(1000))
issues = db.relationship('Issue', backref='client', cascade='all, delete-orphan')
class Issue(db.Model, BasicMixin, ActiveMixin, TimestampMixin):
id = db.Column(UUIDType, primary_key=True, default=uuid.uuid4)
date = db.Column(db.Date, default=datetime.date.today())
client_id = db.Column(UUIDType, db.ForeignKey('clients.id'), nullable=False)
articles = db.relationship('Article', backref='issue', cascade='all, delete-orphan')
class Article(db.Model, BasicMixin, TimestampMixin):
id = db.Column(UUIDType, primary_key=True, default=uuid.uuid4)
title = db.Column(db.String())
body = db.Column(db.String())
issue_id = db.Column(UUIDType, db.ForeignKey('issues.id'), nullable=False)
客户有很多问题,每个问题都有很多文章。
我还有一个ModelView for Article,其中我应该能够按客户端筛选文章列表(按名称选择客户端并仅显示属于此客户的文章)。我该怎么做才能在flask-admin中创建这样的过滤器?
这是使用SQLite的单文件示例:
非常简单的Flask,SQLalchemy和Flask-Admin。感兴趣的课程是FilterByClientTitle
。
class FilterByClientTitle(BaseSQLAFilter):
# Override to create an appropriate query and apply a filter to said query with the passed value from the filter UI
def apply(self, query, value, alias=None):
return query.join(Article.issue).join(Issue.client).filter(Client.title == value)
# readable operation name. This appears in the middle filter line drop-down
def operation(self):
return u'equals'
# Override to provide the options for the filter - in this case it's a list of the titles of the Client model
def get_options(self, view):
return [(client.title, client.title) for client in Client.query.order_by(Client.title)]
Article模型的视图有几个重要的设置/覆盖:
class ArticleView(BaseAdminView):
# ......
# No need to specify the column as we'll set the SQLalchemy filter directly in the filter's apply method
column_filters = [FilterByClientTitle(column=None, name='Client Title')]
# Need this so the filter options are always up-to-date
@expose('/')
def index_view(self):
self._refresh_filters_cache()
return super(ArticleView, self).index_view()
这是完整的示例,需要faker库来获取随机数据:
import datetime
from flask import Flask, url_for
from flask_admin.contrib.sqla import ModelView
from flask_sqlalchemy import SQLAlchemy
from flask_admin import Admin, expose
from faker import Faker
from flask_admin.contrib.sqla.filters import BaseSQLAFilter
from markupsafe import Markup
app = Flask(__name__)
# Create dummy secrey key so we can use sessions
app.config['SECRET_KEY'] = '123456790'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///sample_db.sqlite'
db = SQLAlchemy(app)
@app.route('/')
def index():
return '<a href="/admin/">Click me to get to Admin!</a>'
class Client(db.Model):
__tablename__ = 'clients'
id = db.Column(db.Integer(), primary_key=True)
title = db.Column(db.String(1000))
def __str__(self):
return unicode(self).encode('utf-8')
def __unicode__(self):
return self.title
class Issue(db.Model):
__tablename__ = 'issues'
id = db.Column(db.Integer(), primary_key=True)
date = db.Column(db.Date, default=datetime.date.today())
client_id = db.Column(db.Integer(), db.ForeignKey('clients.id'), nullable=False)
client = db.relationship(Client, backref=db.backref('issues', uselist=True, cascade='all, delete-orphan'))
def __str__(self):
return unicode(self).encode('utf-8')
def __unicode__(self):
return unicode(self.date)
class Article(db.Model):
__tablename__ = 'articles'
id = db.Column(db.Integer(), primary_key=True)
title = db.Column(db.String())
body = db.Column(db.String())
issue_id = db.Column(db.Integer(), db.ForeignKey('issues.id'), nullable=False)
issue = db.relationship(Issue, backref=db.backref('articles', uselist=True, cascade='all, delete-orphan'))
def __str__(self):
return unicode(self).encode('utf-8')
def __unicode__(self):
return '{title} ... {body} ...'.format(title=self.title[:30], body=self.body[:30])
class BaseAdminView(ModelView):
can_view_details = True
named_filter_urls = True
class ClientView(BaseAdminView):
column_list = ('id', 'title')
column_default_sort = ('title', False)
column_filters = ['id', 'title']
class IssueView(BaseAdminView):
column_list = ('id', 'date', 'articles')
column_default_sort = ('date', False)
column_filters = ['id', 'date']
column_formatters = {
'articles': lambda v, c, m, n: Markup('<br>'.join([unicode(a) for a in m.articles])),
}
class FilterByClientTitle(BaseSQLAFilter):
# Override to create an appropriate query and apply a filter to said query with the passed value from the filter UI
def apply(self, query, value, alias=None):
return query.join(Article.issue).join(Issue.client).filter(Client.title == value)
# readable operation name. This appears in the middle filter line drop-down
def operation(self):
return u'equals'
# Override to provide the options for the filter - in this case it's a list of the titles of the Client model
def get_options(self, view):
return [(client.title, client.title) for client in Client.query.order_by(Client.title)]
class ArticleView(BaseAdminView):
column_list = ('title', 'body', 'issue', 'issue.client')
column_labels = {
'issue': 'Issue Date',
'issue.client': 'Client Title'
}
column_default_sort = ('title', False)
def issue_link(self, context, model, name):
return Markup('<a href="{url}" title="Link to Issue">{date}</a>'.format(
url=url_for('issue.index_view', flt1_id_equals=model.issue.id),
date=model.issue.date)
)
def client_link(self, context, model, name):
return Markup('<a href="{url}" title="Link to Client">{title}</a>'.format(
url=url_for('client.index_view', flt1_id_equals=model.issue.client.id),
title=model.issue.client.title)
)
# Display Issue Date and Client Title as links back to their filtered views
column_formatters = {
'title': lambda v, c, m, n: '{} ...'.format(m.title[:20]),
'body': lambda v, c, m, n: '{} ...'.format(m.body[:40]),
'issue': issue_link,
'issue.client': client_link,
}
# No need to specify the column as we'll set the SQLalchemy filter directly in the filter's apply method
column_filters = [FilterByClientTitle(column=None, name='Client Title')]
# Need this so the filter options are always up-to-date
@expose('/')
def index_view(self):
self._refresh_filters_cache()
return super(ArticleView, self).index_view()
admin = Admin(app, template_mode="bootstrap3")
admin.add_view(ClientView(Client, db.session))
admin.add_view(IssueView(Issue, db.session))
admin.add_view(ArticleView(Article, db.session))
def build_sample_db():
fake = Faker()
number_of_clients = 100
number_of_issues_per_client = 5
number_of_articles_per_issues = 5
db.drop_all()
db.create_all()
clients = []
issues = []
articles = []
for client_counter in range(0, number_of_clients):
client_title = fake.last_name()
clients.append({
'id': client_counter,
'title': client_title
})
for issue_counter in range(0, number_of_issues_per_client):
issue_id = number_of_issues_per_client * client_counter + issue_counter
issues.append({
'id': issue_id,
'client_id': client_counter,
'date': fake.date_time_this_decade(before_now=True, after_now=False, tzinfo=None)
})
for article_counter in range(0, number_of_articles_per_issues):
articles.append({
'id': (number_of_articles_per_issues * issue_id) + article_counter,
'issue_id': issue_id,
'title': '{} - {}'.format(client_title, fake.catch_phrase()),
'body': '{} - {}'.format(client_title, fake.text(max_nb_chars=200))
})
db.session.bulk_insert_mappings(Client, clients)
db.session.bulk_insert_mappings(Issue, issues)
db.session.bulk_insert_mappings(Article, articles)
db.session.commit()
if __name__ == '__main__':
build_sample_db()
app.run(debug=True)