Skip to content

Implementation multiple admin panels#1027

Draft
MaximDementyev wants to merge 4 commits into
smithyhq:mainfrom
MaximDementyev:Feature-multiple-admin-panels
Draft

Implementation multiple admin panels#1027
MaximDementyev wants to merge 4 commits into
smithyhq:mainfrom
MaximDementyev:Feature-multiple-admin-panels

Conversation

@MaximDementyev

Copy link
Copy Markdown
Contributor

Problem

It is not possible to add multiple administration panels within the same application

  1. SqlAdmin connects to the FastApi with a fixed name Since query routing is calculated by name, no matter how many Admin applications we create, all links will lead to the first application created. Creating an additional name parameter is not enough, it is necessary to rewrite all url calculations, request.url_for("admin:list") is used everywhere.
  2. The ModelView class uses the session_maker variable, which is a class variable. Therefore, when adding a second application, SqlAdmin session_maker will be overwritten. It turns out that no matter how many SqlAdmin applications we add, they will all work with the database added last.
    fix Multiple Admin Panels for the same fastapi App #919

Solution

  1. In sqladmin.helpers two methods have been created:
    local_url_for - Generate a URL for the specified route, taking router hierarchy into account.
    get_current_router_name - Find the router name in the hierarchy that corresponds to the target router. Traverses the routes of the start router and its nested applications (of type Mount) to find a match with the target router.
    The entire project has updated the use of request.url_for to local_url_for.
  2. session_maker is now an attribute of the class object

This made it possible to run multiple sqladmins within the same application, and also remove the restriction that SqlAdmin can only be connected to the FastApi root application.

from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.orm import declarative_base
from fastapi import FastAPI
from sqladmin import Admin, ModelView
from sqladmin import action

Base = declarative_base()
engine1 = create_engine("sqlite:///example1.db",connect_args={"check_same_thread": False})
engine2 = create_engine("sqlite:///example2.db",connect_args={"check_same_thread": False})
engine3 = create_engine("sqlite:///example3.db",connect_args={"check_same_thread": False})

class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)
    name = Column(String)

class UserAdmin(ModelView, model=User):
    column_list = [User.id, User.name]

Base.metadata.create_all(engine1)
Base.metadata.create_all(engine2)
Base.metadata.create_all(engine3)

app = FastAPI()
admin1 = Admin(app=app, engine=engine1, base_url='/admin/base1', mount_name='base1')
admin1.add_view(UserAdmin)

admin2 = Admin(app=app, engine=engine2, base_url='/admin/base2', mount_name='base2')
admin2.add_view(UserAdmin)

app3 = FastAPI()
app.mount('/api', app3, 'api')
admin3 = Admin(app=app3, engine=engine3, base_url='/admin/base3', mount_name='base3')
admin3.add_view(UserAdmin)

In the example, three administration panels are connected that are connected to different databases
admin1 is accessible via the path /admin/base1
admin2 is accessible via the path /admin/base2
admin3 is accessible via the path /api/admin/base3

If this solution is suitable, I will update the documentation and add tests for new functions.

@mmzeynalli mmzeynalli linked an issue Apr 10, 2026 that may be closed by this pull request
2 tasks
@mmzeynalli

Copy link
Copy Markdown
Member

@MaximDementyev right now, im reviewing smaller PRs, I need enough time for focus to review this, so it can take time. I have not forgotten about this one)

@MaximDementyev

Copy link
Copy Markdown
Contributor Author

The FastApi 0.137.0 update has broken the routers.routers traversal logic.
It will take some time to figure out how to fix this.

If implementing any url search logic seems complicated, you can consider creating a function like this:

def local_url_for(request, name, **kwargs):
   return request.url_for(f"admin:{name}", **kwargs)

This will allow you to collect all the URL search logic in one place, giving users the ability to override this function.
It is very difficult to redefine url_for now, as it affects many files.

@mmzeynalli mmzeynalli mentioned this pull request Jun 23, 2026
1 task
@mmzeynalli

Copy link
Copy Markdown
Member

This is incomplete. I am putting this to draft for now. Here are things that should be considered/fixed

  1. Document breaking changes (especially moving from classvar to object var, i.e. session_maker
  2. The other classvars are still being overriden:
view._admin_ref = self
view.is_async = self.is_async
view.ajax_lookup_url = urljoin(
        self.base_url + "/", f"{view.identity}/ajax/lookup"
)
view.templates = self.templates
view_instance = view(self.session_maker)

So if two admin have different ajax_lookup_urls, it will be messed up

  1. For backwards compatibility, user overriden templates should work. As of today, if user overrode template, it will be admin:admin:list.

  2. Document mount_name param, both in markdown and in the code

  3. Add tests that have two admins

  4. docs/authentication.md and docs/configurations.md still tell users to write request.url_for("admin:index")

  5. If there is unnamed mounts, in get_current_router_name, a nested match returns f"{router.name}:{sub_name}" — if the intermediate Mount has no name, that's the literal string "None:admin", and URL generation blows up.

  6. Dead comparison in the first loop. for router in getattr(start_router, "routes", []): if router == target_router compares a Route/Mount against a Router — never true.

  7. O(route-tree) walk per generated URL. A list page emits dozens of URLs (per-row view/edit/delete, pagination, exports, statics ×13); each one recursively walks the host app's entire route tree. On a large FastAPI app that's thousands of comparisons per request for a value that never changes. The prefix is static — compute it once at mount time (the Admin literally receives mount_name as a parameter!) or cache it in request.scope on first use.

OR:

Inside an admin request, request.app is the admin's Starlette app, so request.app.url_path_for(name, **params).make_absolute_url(request.base_url) produces the correct URL with no name-prefix discovery, no mount_name uniqueness requirement, no tree walk

@mmzeynalli mmzeynalli added waiting-for-feedback Waiting feedback/answer/updates from contributor needs-update There are stuff that needs updated and reviewed again waiting-for-tests Feature is ready, but tests are missing labels Jul 20, 2026
@mmzeynalli
mmzeynalli marked this pull request as draft July 20, 2026 18:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-update There are stuff that needs updated and reviewed again waiting-for-feedback Waiting feedback/answer/updates from contributor waiting-for-tests Feature is ready, but tests are missing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Multiple Admin Panels for the same fastapi App Multiple Admin Instances result in wrong URL's to the views

2 participants