column_filter由grandparent在flask-admin中的属性

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

我的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中创建这样的过滤器?

python flask sqlalchemy flask-admin
1个回答
0
投票

这是使用SQLite的单文件示例:

enter image description here

非常简单的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)
© www.soinside.com 2019 - 2024. All rights reserved.