hack4impact / flask-base

A simple Flask boilerplate app with SQLAlchemy, Redis, User Authentication, and more.
http://hack4impact.github.io/flask-base
MIT License
3.04k stars 481 forks source link

404 error adding flask-admin to project #168

Closed kc1 closed 6 years ago

kc1 commented 6 years ago

I'm trying to add flask-admin to the project:

enter image description here

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?

sandlerben commented 6 years ago

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!

kc1 commented 6 years ago

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.

sandlerben commented 6 years ago

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.

kc1 commented 6 years ago

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

sandlerben commented 6 years ago

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?

kc1 commented 6 years ago

​ posterizer2 - Copy.zip https://drive.google.com/file/d/17J_VL-cYOd2smYqsG_9DB31p22FyTQaU/view?usp=drive_web

On Tue, May 1, 2018 at 3:19 PM, Ben Sandler notifications@github.com wrote:

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?

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/hack4impact/flask-base/issues/168#issuecomment-385762811, or mute the thread https://github.com/notifications/unsubscribe-auth/ABqjLsWEFOJAunrTcIbJuLkG6cIk7Yguks5tuLU0gaJpZM4TuICs .

does that work?

jstacoder commented 6 years ago

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.

kc1 commented 6 years ago

@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.

kc1 commented 6 years ago

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.

kc1 commented 6 years ago

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?
jstacoder commented 6 years ago

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()...

jstacoder commented 6 years ago

Pass endpoint to Admin.add_view, (and I would assume the Admin constructor) to override the blueprint name and prevent collisions.

kc1 commented 6 years ago

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.

jstacoder commented 6 years ago

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.

sandlerben commented 6 years ago

That makes sense. That's a pretty silly issue, we should make an issue on their repo.

jstacoder commented 6 years ago

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...

jstacoder commented 6 years ago

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

kc1 commented 6 years ago

[ capture ]

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

jstacoder commented 6 years ago

The flask admin page will be at the url you specified url=‘/adminz’

kc1 commented 6 years ago

Yes I used 127.0.0.1:5000/adminz. Please see screenshot above

kc1 commented 6 years ago

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
jstacoder commented 6 years ago

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

jstacoder commented 6 years ago

that provides it a static endpoint, and allows flask admin to actually load

kc1 commented 6 years ago

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.

jstacoder commented 6 years ago

Maybe it’s where your running it from try static_folder=‘../static’

kc1 commented 6 years ago

Sorry No change. Is this working for you?

kc1 commented 6 years ago

capture

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
jstacoder commented 6 years ago

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

kc1 commented 6 years ago

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/

kc1 commented 6 years ago

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