404 error adding flask-admin to project
kc1 opened this issue · 30 comments
I'm trying to add flask-admin to the project:
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.
I'm struggling to get the most basic functionality working so now I'm trying to follow https://flask-admin.readthedocs.io/en/v1.1.0/_sources/quickstart.txt
In the app/init.py I have:
from flask_admin import Admin
....
adm = Admin(name='admin2')
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
config[config_name].init_app(app)
....
RQ(app)
adm.init_app(app)
...
from .admin import admin as 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, db
class MyView(ModelView):
@expose('/')
# @login_required
def db(self):
return self.render('admin/db.html')
adm.add_view(MyView(User, db.session))
When I open:
127.0.0.1:5000/db
I get:
AssertionError: A blueprint's name collision occurred between <flask.blueprints.Blueprint object at 0x000000000586C6D8> and <flask.blueprints.Blueprint object at 0x00000000055AFE80>. Both share the same name "admin". Blueprints that are created on the fly need unique names.
What am I doing wrong?
edit:
I changed to:
adm = Admin(name='admin2',endpoint='/db')
However if I try:
127.0.0.1:5000/db/db
I get a 404. I'm assuming you are changing the normal base admin route from 'admin'to 'db'
What now?
Hey kc,
Can you read through what all the parameters mean and see if that helps?
In particular:
:param url:
Base URL
:param endpoint:
Base endpoint name for index view. If you use multiple instances of the `Admin` class with
a single Flask application, you have to set a unique endpoint name for each instance.
:param name:
Application name. Will be displayed in the main menu and as a page title. Defaults to "Admin"
I think all you need to do is set url
to "db" and then you can access the admin page at localhost:5000/db
Let me know if that helps!
Hi Ben,
Thanks for looking at this. I've tried a bunch of variations of this over the past few days. Please see
https://stackoverflow.com/questions/50070979/wrong-dashboard-while-adding-flask-admin-to-project as an example, with various iterations of setting the url, endpoint and name parameters. So far no luck.
Following your instructions above I get:
File "...\app_init_.py", line 100, in create_app
app.register_blueprint(admin_blueprint, url_prefix='/admin')
File "...\lib\site-packages\flask\app.py", line 64, in wrapper_func
return f(self, *args, **kwargs)
File "...\lib\site-packages\flask\app.py", line 946, in register_blueprint
(blueprint, self.blueprints[blueprint.name], blueprint.name)
AssertionError: A blueprint's name collision occurred between <flask.blueprints.Blueprint object at 0x0000000005801748> and <flask.blueprints.Blueprint object at 0x00000000055CF080>. Both share the same name "admin". Blueprints that are created on the fly need unique names.
Maybe flask-admin
can only register an admin
blueprint? If so, perhaps file an issue with that project?
In the meantime, you can unblock yourself by changing this line to something else.
I tried changing it to : app.register_blueprint(admin_blueprint, url_prefix='/ab'). This has no effect and I'm getting the same error. The blueprints in self.blueprints from:
@setupmethod
def register_blueprint(self, blueprint, **options):
"""Registers a blueprint on the application.
.. versionadded:: 0.7
"""
first_registration = False
if blueprint.name in self.blueprints:
assert self.blueprints[blueprint.name] is blueprint, \
'A blueprint\'s name collision occurred between %r and ' \
'%r. Both share the same name "%s". Blueprints that ' \
'are created on the fly need unique names.' % \
(blueprint, self.blueprints[blueprint.name], blueprint.name)
else:
self.blueprints[blueprint.name] = blueprint
self._blueprint_order.append(blueprint)
first_registration = True
blueprint.register(self, options, first_registration)
are:
'myview': <flask.blueprints.Blueprint object at 0x0000000005FC5AC8>, 'main': <flask.blueprints.Blueprint object at 0x0000000005FDA5C0>, 'account': <flask.blueprints.Blueprint object at 0x00000000061FB940>}
...lib\site-packages\flask\app.py:554: Dep
You are still getting the "A blueprint's name collision occurred" error? I have no idea why since I don't see why there would be a name collision. Do you have any way of sharing your code with me?
Looking over the flask_admin code, I think if you just subclass Admin, to give it a new class name, it should register its default view u see the new name ie:
class MyAdmin(Admin):
pass
adm = MyAdmin(name=‘myadmin’)
That should work, unless you are calling app.register_blueprint(admin)
on the flask_admin instance? Because if you are that would cause that error also.
@jstacoder , thanks very much for looking at this. I have not added another blueprint to the app/init file so I am not calling app.register_blueprint(admin) on the flask_admin instance . I changed my code to
class MyAdmin(Admin):
pass
adm = MyAdmin(name='admin2',url='/db')
.......
def create_app(config_name):
........
compress.init_app(app)
RQ(app)
adm.init_app(app)
but I'm getting the same error. When I step through this, I can stop before the error and
Python 3.6.2 |Continuum Analytics, Inc.| (default, Jul 20 2017, 12:30:02) [MSC v.1900 64 bit (AMD64)] on win32
adm
<app.MyAdmin object at 0x0000000005D37400>
adm.name
'admin2'
adm.class
<class 'app.MyAdmin'>
adm.class.name
'MyAdmin'
sorry about the formatting.
I think I'm getting closer. If you change app/admin/init.py to
from flask import Blueprint
# admin = Blueprint('admin', __name__)
admin = Blueprint('admin_blueprint', __name__)
You get past the collision error.
The next problem is if I open
127.0.0.1:5000/db/
Traceback (most recent call last):
File "...lib\site-packages\flask\app.py", line 1997, in __call__
return self.wsgi_app(environ, start_response)
File "...lib\site-packages\flask\app.py", line 1985, in wsgi_app
response = self.handle_exception(e)
File "...lib\site-packages\flask\app.py", line 1540, in handle_exception
reraise(exc_type, exc_value, tb)
File "...lib\site-packages\flask\_compat.py", line 33, in reraise
raise value
File "...lib\site-packages\flask\app.py", line 1982, in wsgi_app
response = self.full_dispatch_request()
File "...lib\site-packages\flask\app.py", line 1614, in full_dispatch_request
rv = self.handle_user_exception(e)
File "...lib\site-packages\flask\app.py", line 1517, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "...lib\site-packages\flask\_compat.py", line 33, in reraise
raise value
File "...lib\site-packages\flask\app.py", line 1612, in full_dispatch_request
rv = self.dispatch_request()
File "...lib\site-packages\flask\app.py", line 1598, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "...lib\site-packages\flask_admin\base.py", line 69, in inner
return self._run_view(f, *args, **kwargs)
File "...lib\site-packages\flask_admin\base.py", line 368, in _run_view
return fn(self, *args, **kwargs)
File "...lib\site-packages\flask_admin\base.py", line 452, in index
return self.render(self._template)
File "...lib\site-packages\flask_admin\base.py", line 308, in render
return render_template(template, **kwargs)
File "...lib\site-packages\flask\templating.py", line 134, in render_template
context, ctx.app)
File "...lib\site-packages\flask\templating.py", line 116, in _render
rv = template.render(context)
File "...lib\site-packages\jinja2\asyncsupport.py", line 76, in render
return original_render(self, *args, **kwargs)
File "...lib\site-packages\jinja2\environment.py", line 1008, in render
return self.environment.handle_exception(exc_info, True)
File "...lib\site-packages\jinja2\environment.py", line 780, in handle_exception
reraise(exc_type, exc_value, tb)
File "...lib\site-packages\jinja2\_compat.py", line 37, in reraise
raise value.with_traceback(tb)
File "...\app\templates\admin\index.html", line 14, in top-level template code
{{ description }}
File "...\app\templates\layouts\base.html", line 38, in top-level template code
{% block content %}
File "...\app\templates\admin\index.html", line 30, in block "content"
{{ dashboard_option('Registered Users', 'admin.registered_users',
File "...lib\site-packages\jinja2\runtime.py", line 579, in _invoke
rv = self._func(*arguments)
File "E:\ENVS\r3\posterizer2\app\templates\admin\index.html", line 4, in template
<a class="column" href="{{ url_for(endpoint) }}">
File "...lib\site-packages\flask\helpers.py", line 333, in url_for
return appctx.app.handle_url_build_error(error, endpoint, values)
File "...lib\site-packages\flask\app.py", line 1805, in handle_url_build_error
reraise(exc_type, exc_value, tb)
File "...lib\site-packages\flask\_compat.py", line 33, in reraise
raise value
File "...lib\site-packages\flask\helpers.py", line 323, in url_for
force_external=external)
File "...lib\site-packages\werkzeug\routing.py", line 1776, in build
raise BuildError(endpoint, values, method, self)
werkzeug.routing.BuildError: Could not build url for endpoint 'admin.registered_users'. Did you mean 'admin_blueprint.registered_users' instead?
But yea, doing that will break all of the urls, at the moment you would need to go through and update and update any calls of url_for(‘admin.SOME_ENDPOINT’)
to point to your new admin_blueprint, url_for(‘admin_blueprint.SOME_ENDPOINT’)
to manually get around that, but I really think the better way to go would be to just figure out how to get the flask_admin.Admin to change its default blueprint name, which I’m pretty surprised didn’t work with my initial suggestion, because it looks like by default it just uses view.__class__.__name__.lower()
...
Pass endpoint
to Admin.add_view, (and I would assume the Admin constructor) to override the blueprint name and prevent collisions.
I changed app/admin/init.py back to the original
from flask import Blueprint
admin = Blueprint('admin', __name__)
In app/init.py:
....
adm = Admin(name='admin2', url='/db', endpoint='/db')
But I've been down this road before, I tried to get help on stack overflow before coming here:
https://stackoverflow.com/questions/50070979/wrong-dashboard-while-adding-flask-admin-to-project
so to summarize if I go to localhost:5000/db , I get the flask-base admin/index.html rather than the flask-admin admin/index.html
based on https://stackoverflow.com/questions/7974771/flask-blueprint-template-folder, this may be caused by
As of Flask 0.8, blueprints add the specified template_folder to the app's searchpath, rather than treating each of the directories as separate entities. This means that if you have two templates with the same filename, the first one found in the searchpath is the one used.
ok worked it out! One big issue is that the default templates from flask_admin wont really work with another view with the admin
endpoint, because even if you can change it you can only actually render it if you override the default views and templates, which kind of defeats the purpose, too many hard coded url_for('admin.static')
calls in flask_admin and too many url_for('admin.*')
in this repo, one would need to make its endpoints variables to allow that to work.
That makes sense. That's a pretty silly issue, we should make an issue on their repo.
I imagine they would say that you just need to override the offending template blocks, although I really feel templates should contain the endpoints as variables, makes things much more customizable much easier. So I would second an issue, but the same fix could just as easily be applied here, and it would work just as well...
ok, as long as the admin blueprint is defined here with these args:
admin = Blueprint(name='admin',import_name='admin', url_prefix='/admin', static_url_path='/static', static_folder='static')
a custom flask admin endpoint works like this:
flask_admin_admin = Admin(endpoint='adminz', name='adz', url='/adminz')
templates and all
I've changed admin/init.py to
from flask import Blueprint
admin = Blueprint(name='admin',import_name='admin', url_prefix='/admin', static_url_path='/static', static_folder='static')
and
in app/init.py:
....
adm = Admin(endpoint='adminz', name='adz', url='/adminz')
I'm still getting the flask-base admin page not the flask-admin base-page
The flask admin page will be at the url you specified url=‘/adminz’
Yes I used 127.0.0.1:5000/adminz. Please see screenshot above
Here is my entire app/init.py
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
class MyView(BaseView):
@expose('/')
# @login_required
def test(self):
return self.render('admin/index.html')
class MyAdmin(Admin):
pass
basedir = os.path.abspath(os.path.dirname(__file__))
mail = Mail()
db = SQLAlchemy()
csrf = CsrfProtect()
compress = Compress()
# adm = Admin(name='admin2',endpoint='/db', url='/db')
# adm = Admin(name='admin2', url='/db', endpoint='/db')
# adm = Admin(endpoint='adminz', name='adz', url='/adminz')
# adm = Admin(name='admin2',url='/db')
# adm = Admin(name='admin2')
# admin = Admin()
# 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
# 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.init_app(app)
# adm.add_view(MyView(name='MyCustomView', url='/db',endpoint='/db'))
# adm.add_view(MyView(User, db.session))
# adm.add_view(ModelView(User, db.session))
# adm.add_view(ModelView(User, db.session))
# Register Jinja template functions
from .utils import register_template_utils
register_template_utils(app)
# Set up asset pipeline
assets_env = Environment(app)
dirs = ['assets/styles', 'assets/scripts']
for path in dirs:
assets_env.append_path(os.path.join(basedir, path))
assets_env.url_expire = True
assets_env.register('app_css', app_css)
assets_env.register('app_js', app_js)
assets_env.register('vendor_css', vendor_css)
assets_env.register('vendor_js', vendor_js)
# Configure SSL if platform supports it
if not app.debug and not app.testing and not app.config['SSL_DISABLE']:
from flask_sslify import SSLify
SSLify(app)
# 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')
return app
ok, loaded up the full setup this time, i see what the last problem was, in admin/views.py admin blueprint should have static_folder='./static'
before we had static_folder='static'
which doesnt work
that provides it a static endpoint, and allows flask admin to actually load
Did you mean admin/init.py? I can't find any 'static' in admin/views.py . If so I tried:
admin = Blueprint(name='admin',import_name='admin', url_prefix='/admin', static_url_path='/static', static_folder='./static')
I'm still getting the screenshot.
Maybe it’s where your running it from try static_folder=‘../static’
Sorry No change. Is this working for you?
The file structure looks like the screenshot.
from flask import Blueprint
from config import basedir
# admin = Blueprint('admin', __name__)
# static_folder = basedir+'/static'
static_folder = basedir+'\\app\\templates'
print(static_folder)
# admin = Blueprint(name='admin',import_name='admin', url_prefix='/admin', static_url_path='/static',static_folder='../static')
admin = Blueprint(name='admin',import_name='admin', url_prefix='/admin', static_url_path='/static',static_folder=static_folder)
print(static_folder) yields
E:\ENVS\r3\posterizer2\app\templates
Static_folder needs to point to the static folder next to templates, yes static_folder=./static is working for me, I am using a windows machine as well, but I am running the stack with docker, I would suggest that
I have no choice but to work in windows. If you download
posterizer2 - Copy.zip
https://drive.google.com/file/d/17J_VL-cYOd2smYqsG_9DB31p22FyTQaU/view?usp=drive_web
does this work for you?
This can't be a windows only issue as I've pushed to heroku and I'm getting the same problem. see for yourself at https://posterizer2.herokuapp.com/adminz/
Ok I decided to start over with the latest copy of flask-base which I cloned and added flask-admin. I can see that the imports have been changed so as you recommended I changed admin/views.py to
admin = Blueprint(name='admin',import_name='admin', url_prefix='/admin', static_url_path='/static', static_folder='./static') - also tried 'static', '../static'
app/init contains:
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
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(endpoint='adminz', name='adz', url='/adminz')
adm.init_app(app)
I've pushed it to heroku:
https://serene-spire-28215.herokuapp.com/adminz