flask-admin: Using standard app factory pattern problematic with Flask-Admin's way of using blueprints
The example below might not be a very good use of the app factory pattern, but imagine that you’re using sqla.ModelView
instead. Then we would have to issue admin.add_view()
within the create_app
flow because we need to pass in the model and database session. Right now I use a workaround found in this SO thread. Is there some way that we could change Flask-Admin’s behavior so that we can follow Flask’s standard application factory workflow?
import flask
import flask_admin
import unittest
class MyAdminView(flask_admin.BaseView):
@flask_admin.expose('/')
def index(self):
self.render('index.html')
admin = flask_admin.Admin()
def create_app():
app = flask.Flask(__name__)
admin.add_view(MyAdminView(name='myview1', endpoint='myview1'))
admin.init_app(app)
return app
class BlueprintCollisionTestCase(unittest.TestCase):
def setUp(self):
self.app = create_app()
def test_1(self):
assert 1 == 1
def test_2(self):
assert 1 == 1
if __name__ == '__main__':
unittest.main()
$ python testflaskadmin.py
.F
======================================================================
FAIL: test_2 (__main__.BlueprintCollisionTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "testflaskadmin.py", line 25, in setUp
self.app = create_app()
File "testflaskadmin.py", line 18, in create_app
admin.add_view(MyAdminView(name='myview1', endpoint='myview1'))
File "/Users/jacob/venvs/myvenv/lib/python3.4/site-packages/Flask_Admin-1.2.0-py3.4.egg/flask_admin/base.py", line 526, in add_view
self.app.register_blueprint(view.create_blueprint(self))
File "/Users/jacob/venvs/myvenv/lib/python3.4/site-packages/flask/app.py", line 62, in wrapper_func
return f(self, *args, **kwargs)
File "/Users/jacob/venvs/myvenv/lib/python3.4/site-packages/flask/app.py", line 885, in register_blueprint
(blueprint, self.blueprints[blueprint.name], blueprint.name)
AssertionError: A blueprint's name collision occurred between <flask.blueprints.Blueprint object at 0x1018ebc18> and <flask.blueprints.Blueprint object at 0x1018eb2b0>. Both share the same name "myview1". Blueprints that are created on the fly need unique names.
----------------------------------------------------------------------
Ran 2 tests in 0.003s
FAILED (failures=1)
About this issue
- Original URL
- State: closed
- Created 9 years ago
- Reactions: 1
- Comments: 16 (12 by maintainers)
Thought I’d chip in with my solution to this issue, as I forget about it every time and I’ll only end up finding this through google again.
I have this function in admin/core.py
my create_app function has this in it, after
app
has been createdand then further on, in the create_app after all my other blueprints have been registered I call the init_admin function and pass it the f_admin object.
Weirdly, if I do the traditional init_app method everything seemed to work when running the site on the dev server, but I would get the blueprint name collisions when running my tests under pytest.
It work out in this way. just for your reference.
OK, I know what’s the problem. It is not about application, contexts or anything like that - it is about multiple calls to
add_view
. Each call adds duplicateBlueprint
and Flask does not like it.Closest analogy would be this:
Only reason why it works -
add_url_rule
can be called multiple times with same URL andregister_blueprint
can not.So, I see two options:
create_app
- add views to the admin beforecreate_app
is called. In lots of the cases this is not possible (you need database session for model views, etc).Admin
instance increate_app
and initialize it there.Also, there’s additional reason why
Admin
updatesself.app
in theAdmin.init_app
method:And I’m not sure how to solve this one, as when
init_app
is called there’s no app context yet, so there’s no “current app” that we can use to store various data.@mrjoes for me it is because of the ability to import “admin” and use it to register new views from de-coupled blueprints. In my case I talk about a CMS and the modules are developed separated and it allows developers to do:
There is another way of allowing de-coupled blueprints to register new views in to admin if it is created inside the context of create_app?
I’ve tried current_app.admin (or current_app.blueprints[‘admin’].add_view and I dont remember the reason of unsuccess)
BTW: I created a lot of customization in the CMS side which allows the use of admin.register(ModelView, DataModel) inspired by the old/dead flask-superadmin
Here’s my solution
admin.py
__init__.py
I am using the lazy initialization for admin.
Now it allows me to initiate admin instance without the need of an app.
This also allows me to use
from myapp import admin
from another modulesI’m using it because that’s the way it’s supposed to be done if you’re using app factories in Flask. I think the idea is to not store state of the Flask app in your extension’s instance, because it might be used by multiple apps. If you read the extension development doc page you’ll see that they recommend storing data on the context (see point number 4 under the extension code example).