starlette-admin icon indicating copy to clipboard operation
starlette-admin copied to clipboard

Bug: FKs Columns won't include in the View.

Open farzbood opened this issue 9 months ago • 1 comments

Describe the bug SQLAlchemy based Models (fsatapi-users: User, AccessToken) foreign key column (user_id) won't appear in the starlette-admin, Table-View grid, so the login process exit (when trying to write token to its associated table with fk's column, user_id) due to an SA-generated exception (see, Additional context).

To Reproduce 1- Add User and AccessToken Models of fastapi-users package to the starlette-admin View(s). 2- Customize Admin, Auth. settings to utilize fastapi-user based flows. 3- Hit the /admin/ endpoint and enter User-credentials of a User, registered by fastapi-user, beforhand.

P.S.

Tables appear in the View, with the default AuthProvider in place (Visitor-Mode per se). Below is the Code-snippet, regarding Admin setup, plus some failed attempts (commented segments for Sync and Async versions) to reach the appropriate Schema!

middlewares = [
    Middleware(SessionMiddleware, secret_key=settings.SESSION_COOKIE_SECRET),
]
logo_url = "/static/common/images/logo.png"
favicon_url = "/static/common/images/favicon.png"
login_logo_url = "/static/user_management/images/user.png"

def get_admin_obj():
    admin = Admin(engine,
                title="IPENprj: Visionium",
                auth_provider=MyAuthProvider(),
                middlewares=middlewares,
                logo_url=logo_url,
                login_logo_url=login_logo_url,
                favicon_url=favicon_url)

    # Add view
    admin.add_view(ModelView(User, icon="fas fa-users"))
    admin.add_view(ModelView(AccessToken, icon="fas fa-lock"))
    # extend_matadata() # A failed attempt to sync the SQLModel and SQLAlchemy <metadata> to resolve cross-model fks!
    admin.add_view(ModelView(Contact, icon="fas fa-list"))
    return admin

# DB-Schema Reflection Utility Functions (async & sync)

async def extend_metadata():

async with engine.connect() as conn:

await conn.run_sync(Contact.metadata.reflect)

def extend_matadata():

User.metadata.reflect(bind=sync_engine,extend_existing=True,resolve_fks=True)

Contact.metadata.reflect(bind=sync_engine,extend_existing=True,resolve_fks=True)

Environment (please complete the following information):

  • Starlette-Admin version: [e.g. 0.14.1]
  • ORM/ODMs: [SQLAlchemy (latest version)]

Additional context Exception(s) thread, during login attempt:

INFO: 127.0.0.1:60430 - "POST /admin/login?next=http%3A%2F%2Flocalhost%3A8000%2Fadmin%2F HTTP/1.1" 500 Internal Server Error ERROR: Exception in ASGI application

  • Exception Group Traceback (most recent call last): | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette_utils.py", line 76, in collapse_excgroups | yield | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette\middleware\base.py", line 174, in call | async with anyio.create_task_group() as task_group: | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\anyio_backends_asyncio.py", line 767, in aexit | raise BaseExceptionGroup( | exceptiongroup.ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception) +-+---------------- 1 ---------------- | Traceback (most recent call last): | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\uvicorn\protocols\http\httptools_impl.py", line 409, in run_asgi | result = await app( # type: ignore[func-returns-value] | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\uvicorn\middleware\proxy_headers.py", line 60, in call | return await self.app(scope, receive, send) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\fastapi\applications.py", line 1054, in call | await super().call(scope, receive, send) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette\applications.py", line 112, in call | await self.middleware_stack(scope, receive, send) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette\middleware\errors.py", line 187, in call | raise exc | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette\middleware\errors.py", line 165, in call | await self.app(scope, receive, _send) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette\middleware\exceptions.py", line 62, in call | await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette_exception_handler.py", line 53, in wrapped_app | raise exc | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette_exception_handler.py", line 42, in wrapped_app | await app(scope, receive, sender) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette\routing.py", line 714, in call
    | await self.middleware_stack(scope, receive, send) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette\routing.py", line 734, in app
    | await route.handle(scope, receive, send) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette\routing.py", line 460, in handle
    | await self.app(scope, receive, send) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette\applications.py", line 112, in call | await self.middleware_stack(scope, receive, send) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette\middleware\errors.py", line 187, in call | raise exc | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette\middleware\errors.py", line 165, in call | await self.app(scope, receive, _send) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette\middleware\base.py", line 173, in call | with recv_stream, send_stream, collapse_excgroups(): | File "C:\Users\IPEN\AppData\Roaming\uv\python\cpython-3.10.16-windows-x86_64-none\lib\contextlib.py", line 153, in exit | self.gen.throw(typ, value, traceback) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette_utils.py", line 82, in collapse_excgroups | raise exc | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette\middleware\base.py", line 175, in call | response = await self.dispatch_func(request, call_next) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette_admin\contrib\sqla\middleware.py", line 24, in dispatch | return await call_next(request) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette\middleware\base.py", line 153, in call_next | raise app_exc | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette\middleware\base.py", line 140, in coro | await self.app(scope, receive_or_disconnect, send_no_error) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette\middleware\sessions.py", line 85, in call | await self.app(scope, receive, send_wrapper) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette\middleware\base.py", line 173, in call | with recv_stream, send_stream, collapse_excgroups(): | File "C:\Users\IPEN\AppData\Roaming\uv\python\cpython-3.10.16-windows-x86_64-none\lib\contextlib.py", line 153, in exit | self.gen.throw(typ, value, traceback) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette_utils.py", line 82, in collapse_excgroups | raise exc | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette\middleware\base.py", line 175, in call | response = await self.dispatch_func(request, call_next) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette_admin\auth.py", line 360, in dispatch | return await call_next(request) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette\middleware\base.py", line 153, in call_next | raise app_exc | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette\middleware\base.py", line 140, in coro | await self.app(scope, receive_or_disconnect, send_no_error) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette\middleware\exceptions.py", line 62, in call | await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette_exception_handler.py", line 53, in wrapped_app | raise exc | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette_exception_handler.py", line 42, in wrapped_app | await app(scope, receive, sender) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette\routing.py", line 714, in call
    | await self.middleware_stack(scope, receive, send) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette\routing.py", line 734, in app
    | await route.handle(scope, receive, send) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette\routing.py", line 288, in handle
    | await self.app(scope, receive, send) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette\routing.py", line 76, in app | await wrap_app_handling_exceptions(app, request)(scope, receive, send) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette_exception_handler.py", line 53, in wrapped_app | raise exc | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette_exception_handler.py", line 42, in wrapped_app | await app(scope, receive, sender) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette\routing.py", line 73, in app | response = await f(request) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette_admin\helpers.py", line 130, in wrapper | return await endpoint(request=request, **kwargs) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\starlette_admin\auth.py", line 241, in render_login | return await self.login( | File "C:\pyprojects\webprjs\FastAPIprjs\IPENeng\src\base\admin.py", line 56, in login | request.session.update({"session": await token_manager.write_token(user)}) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\fastapi_users\authentication\strategy\db\strategy.py", line 45, in write_token | access_token = await self.database.create(access_token_dict) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\fastapi_users_db_sqlalchemy\access_token.py", line 75, in create | await self.session.commit() | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\sqlalchemy\ext\asyncio\session.py", line 1011, in commit | await greenlet_spawn(self.sync_session.commit) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\sqlalchemy\util_concurrency_py3k.py", line 203, in greenlet_spawn | result = context.switch(value) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\sqlalchemy\orm\session.py", line 2032, in commit | trans.commit(_to_root=True) | File "", line 2, in commit | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\sqlalchemy\orm\state_changes.py", line 139, in _go | ret_value = fn(self, *arg, **kw) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\sqlalchemy\orm\session.py", line 1313, in commit | self._prepare_impl() | File "", line 2, in _prepare_impl | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\sqlalchemy\orm\state_changes.py", line 139, in _go | ret_value = fn(self, *arg, **kw) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\sqlalchemy\orm\session.py", line 1288, in _prepare_impl | self.session.flush() | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\sqlalchemy\orm\session.py", line 4353, in flush | self._flush(objects) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\sqlalchemy\orm\session.py", line 4488, in _flush | with util.safe_reraise(): | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\sqlalchemy\util\langhelpers.py", line 146, in exit | raise exc_value.with_traceback(exc_tb) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\sqlalchemy\orm\session.py", line 4449, in _flush | flush_context.execute() | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\sqlalchemy\orm\unitofwork.py", line 466, in execute | rec.execute(self) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\sqlalchemy\orm\unitofwork.py", line 642, in execute | util.preloaded.orm_persistence.save_obj( | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\sqlalchemy\orm\persistence.py", line 76, in save_obj | for table, mapper in base_mapper._sorted_tables.items(): | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\sqlalchemy\util\langhelpers.py", line 1257, in get | obj.dict[self.name] = result = self.fget(obj) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\sqlalchemy\orm\mapper.py", line 4059, in sorted_tables | sorted = sql_util.sort_tables( | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\sqlalchemy\sql\ddl.py", line 1262, in sort_tables | for (t, fkcs) in sort_tables_and_constraints( | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\sqlalchemy\sql\ddl.py", line 1332, in sort_tables_and_constraints | filtered = filter_fn(fkc) | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\sqlalchemy\sql\ddl.py", line 1252, in _skip_fn | if fixed_skip_fn(fk): | File "c:\pyprojects\webprjs\FastAPIprjs\IPENeng.venv\lib\site-packages\sqlalchemy\orm\mapper.py", line 4042, in skip
    | dep = table_to_mapper.get(fk.column.table) | Fil

farzbood avatar Mar 15 '25 09:03 farzbood