Bug: FKs Columns won't include in the View.
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