Custom Pages (non-CRUD features)¶
Not everything is "list/create/edit/delete a table row." For a feature like
an AI-powered PDF extractor, a report generator, or a bulk-import tool, use
kloudmint's RBACBaseView -- it adds a page with its own route and template,
shown in the sidebar as its own menu item, with the same automatic RBAC gating
RBACModelView gives CRUD screens.
1. Define the page¶
# admin_views.py
from kloudmint import RBACBaseView
from sqladmin import expose
from fastapi import Request
class PdfExtractorAdmin(RBACBaseView):
name = "PDF Extractor"
icon = "fa-solid fa-file-pdf"
# resource_name defaults to "PdfExtractor" (class name minus trailing "Admin")
# rbac_actions defaults to ["use"] -- shows up in the permission grid as one checkbox
@expose("/pdf-extractor", methods=["GET", "POST"])
async def pdf_extractor_page(self, request: Request):
if request.method == "POST":
form = await request.form()
file = form["file"]
contents = await file.read()
result = your_ai_pdf_extractor(contents) # call your own AI service here
return await self.templates.TemplateResponse(
request, "pdf_extractor.html", {"result": result}
)
return await self.templates.TemplateResponse(
request, "pdf_extractor.html", {"result": None}
)
self.templates looks up templates the same way sqladmin's own pages do --
put pdf_extractor.html in a templates/ directory registered with your
FastAPI app (a small upload <form> plus wherever you render result).
Shared resource names¶
When two custom pages should share one permission (e.g. a multi-step wizard),
set the same resource_name on both:
class ResumeDraftsAdmin(RBACBaseView):
resource_name = "ResumeIntake"
rbac_actions = ["use"]
2. Register it¶
# main.py
admin.register(PdfExtractorAdmin)
Kloudmint.register() mounts the page and registers it in the permission
dropdown automatically -- no separate register_resource() call needed.
A plain sqladmin BaseView (without RBAC) still goes through
admin.raw.add_base_view() plus a manual register_resource() call; prefer
RBACBaseView unless you have a reason not to.
3. RBAC gating¶
RBACBaseView implements is_accessible and is_visible using
has_permission(request, resource_name, "use"), so the sidebar hides the page
and direct URL visits are blocked for roles without the permission.
Important: is_accessible / is_visible must be plain sync def
methods, not async def. SQLAdmin calls them without await; an async def
returns a coroutine object instead of a bool, and the check silently never denies
anything (the same bug this fix avoided in RBACModelView -- see
src/kloudmint/rbac.py's module docstring).
If a page has multiple actions behind one route (e.g. a POST that only some
"use"-granted users should trigger), add an extra has_permission() check
inside that handler.
More building blocks¶
Related parent/child tables, file uploads, audit trails, detail pages, and column formatters are covered in BuildingBlocks.md.