How do I properly set up flask-admin views with using an application factory?
Asked Answered
S

2

8

I'm trying to setup flask-admin model views with SQLAlchemy against 'user' and 'role' models. Instead of a function admin view I'm getting:

ValueError: Invalid model property name <class 'app.models.Role'>.desc

Stack trace:

Traceback (most recent call last):

File "/Users/dbg/Projects/Python/Current/ziff/flaskbase/manage.py", line 18, in <module>
    app = create_app(os.getenv('APP_CONFIG') or 'default')
  File "/Users/dbg/Projects/Python/Current/ziff/flaskbase/app/__init__.py", line 49, in create_app
    admin.add_view(RoleAdmin(Role, db.session))
  File "/usr/local/share/anaconda/envs/flaskbase27/lib/python2.7/site-packages/Flask_Admin-1.0.9-py2.7.egg/flask_admin/contrib/sqla/view.py", line 288, in __init__
    menu_icon_value=menu_icon_value)
  File "/usr/local/share/anaconda/envs/flaskbase27/lib/python2.7/site-packages/Flask_Admin-1.0.9-py2.7.egg/flask_admin/model/base.py", line 570, in __init__
    self._refresh_cache()
  File "/usr/local/share/anaconda/envs/flaskbase27/lib/python2.7/site-packages/Flask_Admin-1.0.9-py2.7.egg/flask_admin/model/base.py", line 640, in _refresh_cache
    self._refresh_forms_cache()
  File "/usr/local/share/anaconda/envs/flaskbase27/lib/python2.7/site-packages/Flask_Admin-1.0.9-py2.7.egg/flask_admin/model/base.py", line 580, in _refresh_forms_cache
    self._create_form_class = self.get_create_form()
  File "/usr/local/share/anaconda/envs/flaskbase27/lib/python2.7/site-packages/Flask_Admin-1.0.9-py2.7.egg/flask_admin/model/base.py", line 856, in get_create_form
    return self.get_form()
  File "/usr/local/share/anaconda/envs/flaskbase27/lib/python2.7/site-packages/Flask_Admin-1.0.9-py2.7.egg/flask_admin/model/base.py", line 848, in get_form
    return self.scaffold_form()
  File "/usr/local/share/anaconda/envs/flaskbase27/lib/python2.7/site-packages/Flask_Admin-1.0.9-py2.7.egg/flask_admin/contrib/sqla/view.py", line 607, in scaffold_form
    extra_fields=self.form_extra_fields)
  File "/usr/local/share/anaconda/envs/flaskbase27/lib/python2.7/site-packages/Flask_Admin-1.0.9-py2.7.egg/flask_admin/contrib/sqla/form.py", line 427, in get_form
    for name, p in properties:
  File "/usr/local/share/anaconda/envs/flaskbase27/lib/python2.7/site-packages/Flask_Admin-1.0.9-py2.7.egg/flask_admin/contrib/sqla/form.py", line 422, in <genexpr>
    properties = ((x, find(x)) for x in only)
  File "/usr/local/share/anaconda/envs/flaskbase27/lib/python2.7/site-packages/Flask_Admin-1.0.9-py2.7.egg/flask_admin/contrib/sqla/form.py", line 419, in find
    raise ValueError('Invalid model property name %s.%s' % (model, name))
ValueError: Invalid model property name <class 'app.models.Role'>.desc

I am using a flask application factory approach with blueprints. Here's my application factory.

My __init__.py file where I try and create the views on line 49:

from flask import Flask
from flask.ext.bootstrap import Bootstrap
from flask.ext.mail import Mail
from flask.ext.moment import Moment
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.security import Security
from flask.ext.admin import Admin
from flask_s3 import FlaskS3
from flask.ext.cdn import CDN
from flask.ext.assets import Environment
from flask.ext.assets import PythonLoader as PythonAssetsLoader
from config import config


# app setup
bootstrap = Bootstrap()
mail = Mail()
moment = Moment()
db = SQLAlchemy()
security = Security()
admin = Admin()
s3 = FlaskS3()
cdn = CDN()
assets_env = Environment()



def create_app(config_name):
  app = Flask(__name__)
  app.config.from_object(config[config_name])
  app.name = app.config['APP_NAME']
  config[config_name].init_app(app)

  from .models import user_datastore, User, Role
  from .auth.views import RoleAdmin, UserAdmin

  bootstrap.init_app(app)
  mail.init_app(app)
  moment.init_app(app)
  db.init_app(app)
  security.init_app(app, user_datastore)
  admin.init_app(app)
  s3.init_app(app)
  cdn.init_app(app)
  assets_env.init_app(app)

  # Here's where I'm trying to create the views
  admin.add_view(RoleAdmin(Role, db.session))
  admin.add_view(UserAdmin(User, db.session))

  from . import assets
  assets_loader = PythonAssetsLoader(assets)
  for name, bundle in assets_loader.load_bundles().iteritems():
    assets_env.register(name, bundle)

  # attach asset bundles, routes, and custom error pages here
  from main import main as main_blueprint
  app.register_blueprint(main_blueprint)
  import main.assets as main_assets
  assets_loader = PythonAssetsLoader(main_assets)
  for name, bundle in assets_loader.load_bundles().iteritems():
    assets_env.register(name, bundle)

  from auth import auth as auth_blueprint
  app.register_blueprint(auth_blueprint)


  return app

My ModelView classes:

from flask.ext.admin.contrib.sqla import ModelView

__author__ = 'dbg'

class RoleAdmin(ModelView):
  column_display_pk = True
  form_columns = ['id', 'desc']

class UserAdmin(ModelView):
  column_display_pk = True
  form_columns = ['id', 'email', 'active', 'last_login_at', 'login_count', 'roles']

EDIT

Models.py

from app import db
from flask.ext.security import UserMixin, RoleMixin, SQLAlchemyUserDatastore


roles_users = db.Table('roles_users',
                       db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
                       db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))


class Role(db.Model, RoleMixin):
  id = db.Column(db.Integer(), primary_key=True)
  name = db.Column(db.String(80), unique=True)
  description = db.Column(db.String(255))


class User(db.Model, UserMixin):
  id = db.Column(db.Integer(), primary_key=True)
  email = db.Column(db.String(256), unique=True)
  password = db.Column(db.String(36))
  active = db.Column(db.Boolean())
  confirmed_at = db.Column(db.DateTime())
  last_login_at = db.Column(db.DateTime())
  current_login_at = db.Column(db.DateTime())
  last_login_ip = db.Column(db.String(45))
  current_login_ip = db.Column(db.String(45))
  login_count = db.Column(db.Integer())
  roles = db.relationship('Role', secondary=roles_users,
                          backref=db.backref('users', lazy='dynamic'))


user_datastore = SQLAlchemyUserDatastore(db, User, Role)

UPDATE

Found a very helpful write-up here by this good soul. Still stuck. New error.

Traceback (most recent call last):
  File "/Users/dbg/Projects/Python/Current/ziff/flaskbase/manage.py", line 18, in <module>
    app = create_app(os.getenv('APP_CONFIG') or 'default')
  File "/Users/dbg/Projects/Python/Current/ziff/flaskbase/app/__init__.py", line 83, in create_app
    admin.add_view(RoleAdmin(RoleAdmin, db.session))
  File "/usr/local/share/anaconda/envs/flaskbase27/lib/python2.7/site-packages/Flask_Admin-1.0.9-py2.7.egg/flask_admin/contrib/sqla/view.py", line 288, in __init__
    menu_icon_value=menu_icon_value)
  File "/usr/local/share/anaconda/envs/flaskbase27/lib/python2.7/site-packages/Flask_Admin-1.0.9-py2.7.egg/flask_admin/model/base.py", line 570, in __init__
    self._refresh_cache()
  File "/usr/local/share/anaconda/envs/flaskbase27/lib/python2.7/site-packages/Flask_Admin-1.0.9-py2.7.egg/flask_admin/model/base.py", line 632, in _refresh_cache
    self._list_columns = self.get_list_columns()
  File "/usr/local/share/anaconda/envs/flaskbase27/lib/python2.7/site-packages/Flask_Admin-1.0.9-py2.7.egg/flask_admin/model/base.py", line 708, in get_list_columns
    columns = self.scaffold_list_columns()
  File "/usr/local/share/anaconda/envs/flaskbase27/lib/python2.7/site-packages/Flask_Admin-1.0.9-py2.7.egg/flask_admin/contrib/sqla/view.py", line 371, in scaffold_list_columns
    for p in self._get_model_iterator():
  File "/usr/local/share/anaconda/envs/flaskbase27/lib/python2.7/site-packages/Flask_Admin-1.0.9-py2.7.egg/flask_admin/contrib/sqla/view.py", line 310, in _get_model_iterator
    return model._sa_class_manager.mapper.iterate_properties
AttributeError: type object 'RoleAdmin' has no attribute '_sa_class_manager'
Singly answered 13/2, 2015 at 21:5 Comment(7)
Looking at your trace, it seems 'desc' in your RoleAdmin is causing the problem? Do you have that column in the model/table?Fanchet
Please show definition of the Role model.Rob
@Fanchet I have a description column as can be seen in the models.py script I just addedSingly
I just found this and will be trying it.Singly
@davidbgonzalez aren't you supposed to put description instead of just desc? sorry not quite familiar with it.Fanchet
@Fanchet it looks like things are getting munged in the handoff between the admin requesting model information. The write up I linked to is sound and I should have it implemented and testing soon.Singly
@davidbgonzalez first exception was happening because RoleAdmin was not able to find desc column in Role model (you don't have it). Change your RoleAdmin to have form_columns = ['id', 'description'] and it'll work.Rob
S
12

Here is another way using blueprints:

# app/__init__.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_admin import Admin

db = SQLAlchemy()
admin = Admin(name='my-app', template_mode='bootstrap3')


def create_app():
    app = Flask(__name__)

    db.init_app(app)
    admin.init_app(app)

    # Add the admin panel
    from app.admin import bp as admin_bp
    app.register_blueprint(admin_bp)

    return app
# app/admin/__init__.py

from flask import Blueprint

bp = Blueprint('admin_bp', __name__)

from app.admin import routes
# app/admin/routes.py

from flask_admin.contrib.sqla import ModelView
from app import db, admin
from app.models import City

admin.add_view(ModelView(City, db.session))
Striking answered 17/3, 2019 at 11:18 Comment(0)
G
5

Well, this is old, but here is how I do it:

# Inside app/auth/__init__.py (auth module includes admin):

#...
from app import db
from flask import Blueprint
from .my_custom_model_views import MyModelView
from ..models import MyModel
#...
auth = Blueprint('auth', __name__)
from . import views
#...
auth.custom_model_views = []
auth.custom_model_views += [MyModelView(MyModel, db.session)]
#...
from . import forms

# Inside app/__init__.py
# ...
admin = Admin(name='Name', ...)
# ...
def create_app(config_name):
# ...
    app = Flask(__name__)
    # ...
    admin.init_app(app)
    # And here's the important part
    with app.app_context():
        from .auth import auth as auth_blueprint
        admin.add_views(*auth_blueprint.custom_model_views)

The important part is somehow storing the views you want to add (here, I just attach them to the Blueprint) and then adding them WITH APP CONTEXT. Otherwise, everything goes berserk (without app context, it is possible to add the same model multiple times, resulting in ugly name collision errors during instantiation of the app).

It's worth noting that any views you add here will show up in the flask admin menu as if it were a model view, even if it is some other kind of view. You can fix this by changing the view's is_visible and is_accessible methods.

Gabo answered 20/3, 2017 at 21:58 Comment(1)
That bit about not adding app context to avoid name collisions (and instead using init_app) helped me out with a major Flask Admin problem today, so thank you!Anthelmintic

© 2022 - 2024 — McMap. All rights reserved.