dpgaspar / Flask-AppBuilder

Simple and rapid application development framework, built on top of Flask. includes detailed security, auto CRUD generation for your models, google charts and much more. Demo (login with guest/welcome) - http://flaskappbuilder.pythonanywhere.com/
BSD 3-Clause "New" or "Revised" License
4.66k stars 1.36k forks source link

[question] Extending the User model and further classes at the same time #1514

Closed atlasloewenherz closed 3 years ago

atlasloewenherz commented 3 years ago

class diagram

https://gist.githubusercontent.com/atlasloewenherz/c3915d826c0f3fc67070be60cc0dc240/raw/5b1773163a78b3130169854ebb197c8d08b7e519/diamond.png

Extending the User model and Joined Table Inheritance

Hi everyone,

i have the following models:

both the user and the supplier they are inheriting from Party (as in contract party) a contract is a composition of two or more Parties traditionally Supplier and a User.

even though the user and supplier both inherit from the Party class they still have different attributes and behavior.

instead if duplicating the User model my own application User <- Party and the F.A.B User, I'm trying to extend the F.A.B User to "Be" both a Party user and remain the F.A.B User.

here is how I do proceed:

Helper class


class PartyTypes(enum.Enum):
    PARTY = 'party'
    USER = 'user'
    SUPPLIER = 'supplier'

My Application base model using F.A.B Base model


class PrimaryKeyIdMixin(AuditMixin): # uses the F.A.B base class
    __abstract__ = True
    id = Column(Integer, primary_key=True, autoincrement=True)
    #...

Address model

every party have one or many addresses


class Address(PrimaryKeyIdMixin): # uses the F.A.B base class
    __tablename__ = 'address'
    number = Column(String(5), nullable=True)
    city = Column(String(255), nullable=True)
    street = Column(String(255), nullable=True)
    state = Column(String(255), nullable=True)
    zip = Column(String(5), nullable=True)
    country = Column(String(255), nullable=True)
    party_id = Column(Integer, ForeignKey('party.id'))
    party = relationship("Party", back_populates="addresses")

Contract model

Contract has two or Many Parties


class Contract(AppBuilderModel, AuditMixin):
    __tablename__ = 'contract'
   parties = relationship("Party", secondary=association_table)

The Party model


association_table = Table('contract_party_association', metadata,
                          Column('contract_id', Integer, ForeignKey('contract.id')),
                          Column('party_id', Integer, ForeignKey('party.id'))
)

class Party(AppBuilderModel, AuditMixin):
    __tablename__ = 'party'
    id = Column(Integer, primary_key=True, autoincrement=True)
    type = Column(String(20))
    # inheritance
    __mapper_args__ = {
        'polymorphic_identity': PartyTypes.PARTY.value,
        'polymorphic_on': type
    }

    def addresses(cls):
        return relationship("Address", back_populates="party")

I extended the User as the following


from flask_appbuilder.security.sqla.models import User

class AppUser(User,Party):
    __tablename__ = 'ab_user'
    dob = Column(DateTime)
    phone = Column(PhoneNumberType)
    @declared_attr
    def addresses(cls):
        return relationship("Address", back_populates="party")

Error message


raise exc.InvalidRequestError(
sqlalchemy.exc.InvalidRequestError: Class <class 'memoris.sec_models.AppUser'> has multiple mapped bases: [<class 'flask_appbuilder.security.sqla.models.User'>, <class 'memoris.models.Party'>]

Environment

Flask-Appbuilder version: Flask-AppBuilder 3.1.1

pip freeze output:

apispec==3.3.2
attrs==20.2.0
Babel==2.8.0
bcrypt==3.2.0
cffi==1.14.3
click==7.1.2
colorama==0.4.4
defusedxml==0.6.0
dnspython==2.0.0
email-validator==1.1.1
Flask==1.1.2
Flask-AppBuilder==3.1.1
Flask-Babel==1.0.0
Flask-BabelPkg==0.9.6
Flask-Bcrypt==0.7.1
Flask-Cors==3.0.9
Flask-JWT-Extended==3.24.1
Flask-Login==0.4.1
Flask-OpenID==1.2.5
Flask-SQLAlchemy==2.4.4
Flask-WTF==0.14.3
healthcheck==1.3.3
idna==2.10
itsdangerous==1.1.0
Jinja2==2.11.2
jsonschema==3.2.0
MarkupSafe==1.1.1
marshmallow==3.8.0
marshmallow-enum==1.5.1
marshmallow-sqlalchemy==0.23.1
phonenumbers==8.12.12
prison==0.1.3
pycparser==2.20
PyJWT==1.7.1
pyrsistent==0.17.3
python-dateutil==2.8.1
python3-openid==3.2.0
pytz==2020.1
PyYAML==5.3.1
six==1.15.0
speaklater==1.3
SQLAlchemy==1.3.20
SQLAlchemy-Utils==0.36.8
Werkzeug==1.0.1
WTForms==2.3.3

the is the full stack trace


/Users/yelassad/projects/buildr/buildr-venv/bin/python -m run
SECRET KEY ENV VAR NOT SET! SHOULD NOT SEE IN PRODUCTION
Traceback (most recent call last):
  File "/usr/local/Cellar/python@3.9/3.9.0_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/runpy.py", line 197, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/local/Cellar/python@3.9/3.9.0_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/Users/yelassad/projects/buildr/memoris/run.py", line 1, in <module>
    from memoris import app
  File "/Users/yelassad/projects/buildr/memoris/memoris/__init__.py", line 20, in <module>
    from memoris.sec import MySecurityManager
  File "/Users/yelassad/projects/buildr/memoris/memoris/sec.py", line 3, in <module>
    from .sec_models import AppUser
  File "/Users/yelassad/projects/buildr/memoris/memoris/sec_models.py", line 8, in <module>
    class AppUser(User, Party):
  File "/Users/yelassad/projects/buildr/buildr-venv/lib/python3.9/site-packages/flask_sqlalchemy/model.py", line 67, in __init__
    super(NameMetaMixin, cls).__init__(name, bases, d)
  File "/Users/yelassad/projects/buildr/buildr-venv/lib/python3.9/site-packages/flask_sqlalchemy/model.py", line 121, in __init__
    super(BindMetaMixin, cls).__init__(name, bases, d)
  File "/Users/yelassad/projects/buildr/buildr-venv/lib/python3.9/site-packages/sqlalchemy/ext/declarative/api.py", line 76, in __init__
    _as_declarative(cls, classname, cls.__dict__)
  File "/Users/yelassad/projects/buildr/buildr-venv/lib/python3.9/site-packages/sqlalchemy/ext/declarative/base.py", line 131, in _as_declarative
    _MapperConfig.setup_mapping(cls, classname, dict_)
  File "/Users/yelassad/projects/buildr/buildr-venv/lib/python3.9/site-packages/sqlalchemy/ext/declarative/base.py", line 160, in setup_mapping
    cfg_cls(cls_, classname, dict_)
  File "/Users/yelassad/projects/buildr/buildr-venv/lib/python3.9/site-packages/sqlalchemy/ext/declarative/base.py", line 192, in __init__
    self._setup_inheritance()
  File "/Users/yelassad/projects/buildr/buildr-venv/lib/python3.9/site-packages/sqlalchemy/ext/declarative/base.py", line 573, in _setup_inheritance
    raise exc.InvalidRequestError(
sqlalchemy.exc.InvalidRequestError: Class <class 'memoris.sec_models.AppUser'> has multiple mapped bases: [<class 'flask_appbuilder.security.sqla.models.User'>, <class 'memoris.models.Party'>]

any help or guidance will be appreciated!!

stale[bot] commented 3 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. Feel free to reopen it if it's still relevant to you. Thank you