如何在Flask Admin中创建一个url包含变量前缀的ModelView,并使用该变量进行初始筛选



我在一个应用程序的版本一中有以下(简化的(结构,我正在将大部分逻辑移植到版本二的Flask Admin:

  • 门户
    • 这是所有其他资源的基础资源
    • 路由/导致门户的列表视图
    • 路线/new通向创建门户的表单
    • 路由/<portal_slug>/edit导致用于更新门户的表单
  • 账户
    • 直接与门户建立关系,因此v1的应用程序路由如下
    • 路线/<portal_slug>/accounts通向列表视图
    • 路由/<portal_slug>/accounts/new导致创建帐户的表单
    • 路由/<portal_slug>/accounts/<account_id>[/edit]导致更新帐户的表单

我对该结构的推理是用户友好的url,此外,从门户分支似乎只是合乎逻辑的,因为它是应用程序的主要资源。

使用上面的url结构,我有一个url_value_preprocessor函数,它将实体加载到Flask的g变量中,以便在视图中使用。

@app.url_value_preprocessor
def load_url_objects(endpoint, values):
if not values:
return
if "portal_slug" in values:
g.portal = Portal.query.filter_by(slug=values.pop("portal_slug", None)).first()
if g.portal:
# save portal_id to session for use elsewhere
session["portal_id"] = g.portal.id
if "account_id" in values:
g.account = g.portal.accounts.filter_by(id=values.pop("account_id", None)).first()

我的问题

使用Flask Admin时,如何维护此url结构?我该如何实现这种行为?

障碍1:对于连接到门户的所有视图(帐户、报告、作业等(,portal_slug是一个过滤器,需要在之前应用以列出视图呈现。这是因为每个门户都有额外的自定义字段,这些字段需要在所有门户的静态字段之上的列表视图中呈现。

障碍2:在导航栏(而不是内容区(中提供下拉菜单,无论用户处于哪个视图中,都可以切换门户。每当单击任何导航栏链接以获取附加到门户的视图时,这也可用于选择初始门户(或默认为第一个门户(。这就是我在上面使用session["portal_id"] = g.portal.id的原因,以便使最终用户能够轻松地在选项卡之间导航(维护上次查看的门户(。

障碍3:我想无论何时排序、搜索、过滤,让所有的url都能反映这种行为都是非常困难的。或者更可能的是,我只是无法弄清楚需要覆盖的所有函数。

结构的最小工作示例

from flask_admin.contrib.sqla import ModelView
from flask_sqlalchemy import SQLAlchemy
from flask import Flask, session, g
from flask_admin import Admin
from sqlalchemy import event
from slugify import slugify
app = Flask(__name__)
db = SQLAlchemy(app)
admin = Admin(app, url="/", template_mode="bootstrap4")
class Portal(db.Model):
__tablename__ = "portals"

id = db.Column(db.Integer, primary_key=True)
display_name = db.Column(db.String(100), nullable=False, unique=True)
slug = db.Column(db.String(20), nullable=False, unique=True)
accounts = db.relationship("Account", backref="portal", lazy="dynamic", cascade="all, delete")

@staticmethod
def slugify(target, new_value, old_value, initiator):
if new_value and (not target.slug or new_value != old_value):
new_slug = slugify(new_value, lowercase=True, max_length=20, word_boundary=True, save_order=True)
target.slug = new_slug
class Account(db.Model):
__tablename__ = "accounts"

id = db.Column(db.Integer, primary_key=True)
portal_id = db.Column(db.Integer, db.ForeignKey("portals.id"), nullable=False)
client_name = db.Column(db.String(50), nullable=False)
event.listen(Portal.display_name, "set", Portal.slugify, retval=False)
@app.shell_context_processor
def load_url_objects(endpoint, values):
if not values:
return
if "portal_slug" in values:
g.portal = Portal.query.filter_by(slug=values.pop("portal_slug", None)).first()
if g.portal:
# save portal_id to session for top navbar link routes
session["portal_id"] = g.portal.id
if "account_id" in values:
g.account = g.portal.accounts.filter_by(id=values.pop("account_id", None)).first()
class PortalView(ModelView): pass  # /portals
class AccountView(ModelView): pass  # currently /accounts, but need it to be /<portal_slug>/accounts for all links related to account(s)
admin.add_view(PortalView(Portal, db.session, name="Portals", endpoint="portals"))
admin.add_view(AccountView(Account, db.session, name="Accounts", endpoint="accounts"))
@app.before_first_request
def prep_db():
db.create_all()
if __name__ == "__main__":
app.config.update({
"SQLALCHEMY_TRACK_MODIFICATIONS": False,
"SQLALCHEMY_DATABASE_URI": "sqlite:///:memory:"
})
app.run(debug=True)

我有一个类似的问题,我通过覆盖get_query和get_query_count方法来获得我的index_view过滤器并覆盖我的端点来解决它。

您的路线为/<portal_slug>accounts/<account_id>[/edit]-我建议您@express("/<account_id>/edit并重写您的edit_view函数

class PostView(ModelVieW):
...
all your main required code goes here 
including your expose..
@expose("/<account_id>/edit")
def my_edit_view(self):
... ...
you need to take the account_id param and pass it
to flask.request as param id=<account_id>, this is
how flask-admin read the id.
return super().edit_view()
class PostPublishView(PostView):
def is_visible(self):
return False
def get_query(self):
return self.session.query(self.model).filter(self.model.status=='publish')
def get_count_query(self):
return self.session.query(func.count('*')).filter(self.model.status=='publish')

admin.add_view(PostPublishView(Post, db.session, name="Publish", endpoint='/post/publish'))
admin.add_view(PostView(Post, db.session, name='Post', endpoint='post'))

最新更新