pallets-eco / flask-admin

Simple and extensible administrative interface framework for Flask
https://flask-admin.readthedocs.io
BSD 3-Clause "New" or "Revised" License
5.73k stars 1.57k forks source link

Scaffolding filters with polymorphic inheritance leads to AttributeError #1838

Open yujinio opened 5 years ago

yujinio commented 5 years ago

Hi!

I'm getting 'Join' object has no attribute 'name' error when trying to add inherited object as one of the column filters.

visible_name = '%s / %s' % (self.get_column_name(attr.prop.target.name),
                                                self.get_column_name(p.key))

If you look here, you may notice, that attr.prop.target may be a JOIN object - that's my case when I've got a Candidate model as a child of User model, trying to implement it as a column filter.

class User(db.Model):
    __mapper_args__ = {
        'polymorphic_identity': 'user',
        'polymorphic_on': role,
    }

class Candidate(User):
    __mapper_args__ = {
        'polymorphic_identity': 'candidate',
    }
ljluestc commented 1 month ago

from flask_sqlalchemy import SQLAlchemy
from flask_admin.contrib.sqla import ModelView

db = SQLAlchemy()

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    role = db.Column(db.String(50))

    __mapper_args__ = {
        'polymorphic_identity': 'user',
        'polymorphic_on': role,
    }

class Candidate(User):
    __mapper_args__ = {
        'polymorphic_identity': 'candidate',
    }

class MyModelView(ModelView):

    def scaffold_filters(self, name):
        filters = []
        for attr in self.model._sa_class_manager.mapper.iterate_properties:
            if hasattr(attr, 'direction'):
                if hasattr(attr.prop, 'target'):
                    # Check if attr.prop.target is a join object
                    if isinstance(attr.prop.target, db.Table):
                        visible_name = '%s / %s' % (self.get_column_name(attr.key), attr.prop.target.name)
                    else:
                        visible_name = '%s / %s' % (self.get_column_name(attr.prop.target.name),
                                                    self.get_column_name(attr.key))
                    filters.append((attr.key, visible_name))
                else:
                    filters.append((attr.key, self.get_column_name(attr.key)))
            else:
                filters.append((attr.key, self.get_column_name(attr.key)))
        return filters

    def get_column_name(self, name):
        # Customize column names as needed
        return name.capitalize()

# Add your views to the admin
from flask_admin import Admin
from flask import Flask

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///mydatabase.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)

admin = Admin(app)
admin.add_view(MyModelView(User, db.session))
admin.add_view(MyModelView(Candidate, db.session))

if __name__ == '__main__':
    app.run(debug=True)