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.74k stars 1.57k forks source link

ModelView creation fails when an inline model is used with a SQLAlchemy model subclass #875

Open klothe opened 9 years ago

klothe commented 9 years ago

If you define a parallel hierarchy of model classes where class A has a subclass B, class C has a subclass D, and A has a relationship to C,

A ---> C
|      |
B      D

it is not possible to instantiate a ModelView of B with D as an inline model, like this:

class MyModelView(ModelView):
    inline_models = (D,)
admin.add_view(MyModelView(B, Session))

This fails with the error:

Exception: Cannot find reverse relation for model <class '__main__.D'>

Here is the full code to reproduce the error with Flask-Admin 1.1.0 and SQLAlchemy 1.0.4:

from flask import Flask
from flask.ext.admin import Admin
from flask.ext.admin.contrib.sqla import ModelView
from sqlalchemy import Column, String, create_engine, Integer, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session, relationship

app = Flask(__name__)

engine = create_engine('sqlite:///:memory:', echo=True)
Session = scoped_session(sessionmaker(bind=engine))
Base = declarative_base()
Base.metadata.bind = engine

class C(Base):
    __tablename__ = 'c'
    id = Column(Integer, primary_key=True)
    b_id = Column(Integer, ForeignKey('a.id'))
    type_name = Column('type_name', String, nullable=False)
    __mapper_args__ = {'polymorphic_on': type_name, 'polymorphic_identity': 'c'}

class D(C):
    __mapper_args__ = {'polymorphic_identity': 'd'}
    x = Column(String(100))

class A(Base):
    __tablename__ = 'a'
    id = Column(Integer, primary_key=True)
    type_name = Column('type_name', String, nullable=False)
    __mapper_args__ = {'polymorphic_on': type_name, 'polymorphic_identity': 'a'}
    c = relationship(C, backref='a')

class B(A):
    __mapper_args__ = {'polymorphic_identity': 'b'}

Base.metadata.create_all()

admin = Admin(app)
class MyModelView(ModelView):
    inline_models = (D,)
admin.add_view(MyModelView(B, Session))

app.run(debug=True)

If the relationship from A to C is moved "downward", i.e. replaced with a relationship from B to D, there is no error.

mrjoes commented 9 years ago

Flask-Admin can't find relationship from model C to model B. Add backref:

class B(A):
    __mapper_args__ = {'polymorphic_identity': 'b'}
    c = relationship(C, backref='b')
klothe commented 9 years ago

Sorry, my original description of the problem was wrong--it just happens that forgetting the backref causes the same error message. I've updated the example code.

mrjoes commented 9 years ago

OK, sorry for taking to long to respond.

InlineModels only work with directly related models. In your case it is A to B (or vice a versa), C to D, A to C. B and D are not directly related and Flask-Admin cannot figure out what to do.

cancan101 commented 5 years ago

I am having a similar issue. I have a model Invitation that has a relationship to Employee. Employee is a subclass of User. Right now the inline model fails. That being said, the relationship does not in any way involve User. I would think this should work and that flask admin should not just fall back to the base class here:

        # Find property from target model to current model
        # Use the base mapper to support inheritance
        target_mapper = info.model._sa_class_manager.mapper.base_mapper