
I'm trying to extend the flask-base project https://github.com/hack4impact/flask-base/tree/master/app. This uses the the application factory pattern in app/init.py and blueprints.
In the app/init.py I have:
import os
from flask import Flask
from flask_mail import Mail
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from flask_assets import Environment
from flask_wtf import CsrfProtect
from flask_compress import Compress
from flask_rq import RQ
from flask_admin import Admin, BaseView, expose
from flask_admin.contrib.sqla import ModelView
# from app.models import User
from config import config
from .assets import app_css, app_js, vendor_css, vendor_js
basedir = os.path.abspath(os.path.dirname(__file__))
mail = Mail()
db = SQLAlchemy()
csrf = CsrfProtect()
compress = Compress()
# Set up Flask-Login
login_manager = LoginManager()
login_manager.session_protection = 'strong'
login_manager.login_view = 'account.login'
from app.models import User
def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config[config_name])
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# not using sqlalchemy event system, hence disabling it
with app.app_context():
m =app.url_map
config[config_name].init_app(app)
# Set up extensions
mail.init_app(app)
db.init_app(app)
login_manager.init_app(app)
csrf.init_app(app)
compress.init_app(app)
RQ(app)
# adm = Admin(app, name='MyAPP')
adm = Admin(endpoint='adminz', name='adz', url='/adminz')
......
# Create app blueprints
from .main import main as main_blueprint
app.register_blueprint(main_blueprint)
from .account import account as account_blueprint
app.register_blueprint(account_blueprint, url_prefix='/account')
from .admin import admin as admin_blueprint
# from .admin import admin_blueprint
app.register_blueprint(admin_blueprint, url_prefix='/admin')
return app
templates/admin/db.html:
<p>Hello world</p>
To the admin views (https://github.com/hack4impact/flask-base/blob/master/app/admin/views.py) I've added :
from flask_admin import Admin, BaseView, expose
from flask_admin.contrib.sqla import ModelView
from app import adm as adm, db
class MyView(ModelView):
@expose('/')
def db(self):
return self.render('admin/db.html')
# admin management setup
@main.route('/db')
def db():
adm.add_view(MyView(User, db.session))
I'm getting the admin dashboard not the flask-admin basic view when I open:
127.0.0.1:5000/db

What am I doing wrong?
EDIT:
following your directions I changed create_app to start with:
def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config[config_name])
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# not using sqlalchemy event system, hence disabling it
# adm = Admin(name='admin2', endpoint='/db', url='/db', template_mode='bootstrap3',base_template='admin/db.html')
config[config_name].init_app(app)
# Set up extensions
mail.init_app(app)
db.init_app(app)
login_manager.init_app(app)
csrf.init_app(app)
compress.init_app(app)
RQ(app)
adm = Admin(app, name='MyAPP')
# adm = Admin(endpoint='adminz', name='adz', url='/adminz')
adm.add_view(MyView(User, db.session, endpoint='db'))
This results in :
File "....flask\app.py", line 946, in register_blueprint (blueprint, self.blueprints[blueprint.name], blueprint.name) AssertionError: A blueprint's name collision occurred between and . Both share the same name "admin". Blueprints that are created on the fly need unique names.
EDIT2:
to the end of create_app I've added:
# Create app blueprints
from .main import main as main_blueprint
app.register_blueprint(main_blueprint)
from .account import account as account_blueprint
app.register_blueprint(account_blueprint, url_prefix='/account')
from .admin import admin as admin_blueprint
# from .admin import admin_blueprint
app.register_blueprint(admin_blueprint, url_prefix='/admin')
# app.register_blueprint(admin_blueprint, url_prefix='/ab')
with app.app_context():
m =app.url_map
return app
I'm not sure what you want to see but m.rules gives:
<Rule '/account/manage/change-password' (HEAD, GET, OPTIONS, POST) -> account.change_password>,
<Rule '/account/manage/change-email' (HEAD, GET, OPTIONS, POST) -> account.change_email_request>,
<Rule '/account/manage/info' (HEAD, GET, OPTIONS, POST) -> account.manage>,
<Rule '/account/confirm-account' (HEAD, GET, OPTIONS) -> account.confirm_request>,
<Rule '/account/reset-password' (HEAD, GET, OPTIONS, POST) -> account.reset_password_request>,
<Rule '/account/unconfirmed' (HEAD, GET, OPTIONS) -> account.unconfirmed>,
<Rule '/account/register' (HEAD, GET, OPTIONS, POST) -> account.register>,
<Rule '/account/logout' (HEAD, GET, OPTIONS) -> account.logout>,
<Rule '/account/manage' (HEAD, GET, OPTIONS, POST) -> account.manage>,
<Rule '/account/login' (HEAD, GET, OPTIONS, POST) -> account.login>,
<Rule '/admin/_update_editor_contents' (OPTIONS, POST) -> admin.update_editor_contents>,
<Rule '/admin/invite-user' (HEAD, GET, OPTIONS, POST) -> admin.invite_user>,
<Rule '/admin/new-user' (HEAD, GET, OPTIONS, POST) -> admin.new_user>,
<Rule '/admin/users' (HEAD, GET, OPTIONS) -> admin.registered_users>,
<Rule '/get_session_job_value' (HEAD, GET, OPTIONS) -> main.get_session_job_value>,
<Rule '/cl_confirm_chrome' (HEAD, GET, OPTIONS, POST) -> main.cl_confirm_chrome>,
<Rule '/render_png' (HEAD, GET, OPTIONS) -> main.render_png>,
<Rule '/selected' (HEAD, GET, OPTIONS) -> main.selected>,
<Rule '/cl_dash' (HEAD, GET, OPTIONS, POST) -> main.cl_dash>,
<Rule '/about' (HEAD, GET, OPTIONS) -> main.about>,
<Rule '/admin/' (HEAD, GET, OPTIONS) -> admin.index>,
<Rule '/dash' (HEAD, GET, OPTIONS) -> main.dash>,
<Rule '/jobs' (HEAD, GET, OPTIONS) -> main.get_jobs>,
<Rule '/' (HEAD, GET, OPTIONS) -> main.index>,
<Rule '/account/manage/change-email/<token>' (HEAD, GET, OPTIONS, POST) -> account.change_email>,
<Rule '/admin/user/<user_id>/change-account-type' (HEAD, GET, OPTIONS, POST) -> admin.change_account_type>,
<Rule '/admin/user/<user_id>/change-email' (HEAD, GET, OPTIONS, POST) -> admin.change_user_email>,
<Rule '/admin/user/<user_id>/_delete' (HEAD, GET, OPTIONS) -> admin.delete_user>,
<Rule '/admin/user/<user_id>/delete' (HEAD, GET, OPTIONS) -> admin.delete_user_request>,
<Rule '/admin/user/<user_id>/info' (HEAD, GET, OPTIONS) -> admin.user_info>,
<Rule '/account/join-from-invite/<user_id>/<token>' (HEAD, GET, OPTIONS, POST) -> account.join_from_invite>,
<Rule '/account/confirm-account/<token>' (HEAD, GET, OPTIONS) -> account.confirm>,
<Rule '/account/reset-password/<token>' (HEAD, GET, OPTIONS, POST) -> account.reset_password>,
<Rule '/admin/static/<filename>' (HEAD, GET, OPTIONS) -> admin.static>,
<Rule '/admin/user/<user_id>' (HEAD, GET, OPTIONS) -> admin.user_info>,
<Rule '/results/<job_key>' (HEAD, GET, OPTIONS) -> main.get_results>,
<Rule '/static/<filename>' (HEAD, GET, OPTIONS) -> static>
EDIT 3:
I have to say that is an incredible answer! You have really taught me a lot. I have replaced the urls with the rules above following your directions. My original plan 10 days ago was just to use the basic flask-admin CRUD functionality. I'm not interested in the db.html template (its just something I tried).
anyway trying to change the name of the admin blueprint ( your number 1). I had tried this before changing app/admin/init.py to :
from flask import Blueprint
admin = Blueprint('admin_blueprint', __name__)
from . import views # noqa
Now when I open
http://127.0.0.1:5000/adminz/
I get a 404 error
FINAL EDIT:
The problem was solved by https://chat.stackoverflow.com/users/5819113/diego-quintana who explained that there was a conflict between flask-admin which creates a blueprint and the flask-base admin blueprint. By changing both the name of the blueprint and the static file folder of the flask-base project. Flask-admin could work without being overridden. Please see https://github.com/kc1/flask-base
In a world of micro-services and APIs, Flask-Admin solves the boring problem of building an admin interface on top of an existing data model. With little effort, it lets you manage your web service's data through a user-friendly interface.
You should be initializing Admin and registering views and blueprints inside create_app. Check if this works for you.
# app factory
def create_app(config_name):
# configure current app
app = Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app(app)
# wrap app with extensions
...
# admin.init_app(app) does not work and flask-admin
# should be instantiated inside create_app()
# see https://github.com/flask-admin/flask-admin/issues/910#issuecomment-115003492
# for details
admin = Admin(app, name='MyAPP')
...
# import models
from .models.user import User
# more imports happening here
# import flask-admin views to be used in the admin panel
from .admin.views import MyView
# register admin view forms
admin.add_view(MyView(name='MyCustomView', endpoint='db'))
# register blueprints
# ...
# implementation of the app factory pattern
return app
EDIT:
What I believe is happening is that
admin living in /adminflask-admin in the app, but it clashes with the existing blueprintYou can achieve this doing two things:
blueprint name in the repo to something different from admin, since flask-admin clashes with it. (Reading from your github issue it seems the are a lot of hardcoded internals for admin.static, which makes changing the current admin blueprint easier.the anatomy of a Blueprint is kinda like this
# app/myblueprint/__init__.py
from flask import Blueprint
# a random blueprint
myblueprint = Blueprint(name='mycustomblueprint',
import_name=__name__, # name of the file
static_folder='static', # a folder inside app/myblueprint/static
template_folder='templates', # a folder inside app/myblueprint/templates
static_url_path='/static', # this is what mycustomblueprint.static will point to, and if the name is admin it will be admin.static, thus colliding with flask-admin
url_prefix='/myblueprintprefix', # this will be appended to each view inside your blueprint, i.e. a view '/foo' will get converted into '/myblueprintprefix/foo' in your url mappings
subdomain=None,
url_defaults=None,
root_path=None)
from . import views # import the views inside app/myblueprint/views.py
then, you import it inside create_app as
from .myblueprint import myblueprint as my_blueprint
app.register_blueprint(my_blueprint) # notice I've defined url_prefix in the Blueprint definition. You can do it at registration time, it's up to you
tl;dr: change the admin blueprint since it's clashing with flask-admin
flask-admin works based in views, and the pattern to generate admin views is by importing them and passing an url parameter that gets appended to the /admin endpoint (where flask-admin lives).
In this case, you can think of two flavours (more but for the sake of the example it's okay)
ModelView, which you use to create custom CRUD views and takes both a model and a db.session object. BaseView which you use to extend a generic view inside the admin blueprint used by flask-admin. This means, if you want to render your own db.html file inside the flask-admin views, you have to do:
# app/modelviews/mycustomviews.py
from flask_admin import BaseView, expose
class DBView(BaseView): # notice I'm using BaseView and not ModelView
@expose('/')
def index(self):
return self.render('modelviews/db.html') # this file should live in app/templates/modelviews/db.html
and inside create_app
# register admin view forms
from .modelviews import DBView
admin.add_view(DBView(name='MyCustomView', endpoint='db')) # this will show up in your `flask-admin` main view as MyCustomView, and it will live in {host}/admin/db
You can also check in your url_map parameter of the flask app in context that you have. You don't need this bit in your create_app
with app.app_context():
m =app.url_map
I mentioned it because it could help you debug your views inside the python repl. Import your app, and follow the gist I've provided. The url_map should return something like a list of <Rules>
[<Rule '/admin/' (OPTIONS, HEAD, GET) -> admin.index>,
<Rule '/admin/db' (OPTIONS, HEAD, GET) -> dbview.index>]
This way you can confirm that your view lives where it should. Hope this helps.
Use url instead of endpoint
adm = Admin(name='test',url='/db')
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With