Saltar a contenido
Traducción automática supervisada

Este contenido se traduce mediante generación automática guiada por glosarios y guías de estilo revisados por personas. Dado que el texto no se revisa manualmente línea por línea, pueden producirse errores ocasionales o expresiones poco naturales.

En caso de cualquier discrepancia, la versión en inglés constituye la autoridad y la fuente de referencia.

Leer la versión original en inglés

Múltiples instancias de Admin

Cada instancia de Admin que construya es una sub-aplicación Starlette autocontenida. Monte tantas como necesite, cada una con su propio base_url, route_name, proveedor de autenticación y vistas.

from sqlalchemy import create_engine
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.sessions import SessionMiddleware
from starlette.requests import Request
from starlette_admin.auth import AdminUser, AuthProvider, LoginFailed
from starlette_admin.contrib.sqla import Admin, ModelView

from myapp.models import Order, Post, User

engine = create_engine("sqlite:///app.sqlite")
app = Starlette()

STAFF = {"staff": "staffpass"}
SUPERADMINS = {"root": "rootpass"}


class StaffAuth(AuthProvider):
    async def login(
        self, username: str, password: str, remember_me: bool, request: Request
    ) -> None:
        if STAFF.get(username) == password:
            request.session["user"] = username
            return
        raise LoginFailed("Invalid username or password")

    async def authenticate(self, request: Request) -> AdminUser | None:
        username = request.session.get("user")
        return AdminUser(username=username) if username in STAFF else None

    async def logout(self, request: Request) -> None:
        request.session.clear()


class SuperAdminAuth(AuthProvider):
    async def login(
        self, username: str, password: str, remember_me: bool, request: Request
    ) -> None:
        if SUPERADMINS.get(username) == password:
            request.session["user"] = username
            return
        raise LoginFailed("Invalid username or password")

    async def authenticate(self, request: Request) -> AdminUser | None:
        username = request.session.get("user")
        return AdminUser(username=username) if username in SUPERADMINS else None

    async def logout(self, request: Request) -> None:
        request.session.clear()


staff_admin = Admin(
    engine,
    title="Staff Admin",
    base_url="/staff",
    route_name="staff_admin",
    auth_provider=StaffAuth(),
    secret_key="staff-secret-change-me",
    middlewares=[Middleware(SessionMiddleware, secret_key="staff-secret-change-me")],
)
staff_admin.add_view(ModelView(Order))
staff_admin.add_view(ModelView(Post))
staff_admin.mount_to(app)

root_admin = Admin(
    engine,
    title="Super Admin",
    base_url="/root",
    route_name="root_admin",
    auth_provider=SuperAdminAuth(),
    secret_key="root-secret-change-me",
    middlewares=[Middleware(SessionMiddleware, secret_key="root-secret-change-me")],
)
root_admin.add_view(ModelView(Order))
root_admin.add_view(ModelView(User))
root_admin.mount_to(app)

En este ejemplo, /staff muestra una página de inicio de sesión respaldada por StaffAuth y /root muestra otra independiente respaldada por SuperAdminAuth. Iniciar sesión en uno no otorga acceso al otro: cada SessionMiddleware firma su cookie con su propio secret_key, por lo que cada instancia de Admin solo lee los datos de sesión que escribió su propio proveedor de autenticación.

base_url y route_name

base_url y route_name son parámetros del constructor de las clases Admin y BaseAdmin en starlette_admin/base.py. Sus valores predeterminados son /admin y "admin":

def __init__(
    self,
    title: str = "Admin",
    base_url: str = "/admin",
    route_name: str = "admin",
    ...
)
  • base_url establece el prefijo de ruta donde se monta el admin. Se pasa directamente a la llamada interna app.mount(self.base_url, app=admin_app, name=self.route_name), por lo que debe ser único por instancia. De lo contrario, un montaje ocultaría al otro.
  • route_name es el nombre bajo el cual Starlette registra el montaje. Cada URL que genera el admin, ya sea para listas, detalles, ediciones, exportaciones o recursos estáticos, proviene de request.url_for(route_name + ":list", ...), y cada plantilla de página lee request.app.state.ROUTE_NAME para obtener el prefijo correcto al construir enlaces.

mount_to construye una sub-aplicación Starlette nueva para cada instancia de admin, de modo que el middleware, las rutas y las variables globales de plantilla permanecen aislados. Admin no es un singleton a nivel de proceso: construya tantas instancias independientes como necesite su aplicación.

Warning

Asigne a cada Admin un route_name distinto. El router de Starlette resuelve url_for("admin:list", ...) haciendo coincidir el nombre del montaje, de modo que dos admins que compartan un route_name dejan la aplicación padre con dos montajes bajo el mismo nombre, y url_for se resuelve con el primero que Starlette encuentre. Todos los enlaces internos del segundo admin, incluidos los enlaces de edición, los recursos estáticos y los endpoints de exportación, apuntarán entonces silenciosamente al base_url del primer admin.

Compartir vistas frente a definir vistas separadas

add_view recibe una instancia de vista y la modifica durante la configuración. Para una BaseModelView, esa configuración vincula callbacks internos al admin con el que se registra, incluida la forma en que los campos HasOne y HasMany resuelven los enlaces a registros relacionados.

Si registra la misma instancia de vista en dos admins, la segunda llamada a add_view sobrescribe esos callbacks, por lo que los enlaces de relación en las páginas del primer admin se resolverán contra las vistas y URLs del segundo admin.

Para evitarlo, asigne a cada admin una instancia nueva de la clase ModelView. La clase no contiene estado específico del admin; solo lo contienen las instancias:

staff_admin.add_view(ModelView(Order))
root_admin.add_view(ModelView(Order))  # separate instance of the same class; this is safe

Cuando los dos admins necesitan comportamientos diferentes, como reglas de visibilidad distintas o permisos de can_delete, escriba una subclase para cada uno en lugar de modificar una instancia compartida en tiempo de ejecución:

class StaffOrderView(ModelView):
    fields_default_sort = ["-created_at"]

    def can_delete(self, request: Request) -> bool:
        return False


class RootOrderView(ModelView):
    fields_default_sort = ["-created_at"]


staff_admin.add_view(StaffOrderView(Order))
root_admin.add_view(RootOrderView(Order))

¿Qué sigue?

  • Autenticación: El contrato completo de AuthProvider y OAuthProvider.
  • Puntos de extensión: Todas las demás superficies conectables disponibles en la clase Admin.
  • Inicio rápido: La configuración fundamental de un solo admin sobre la cual se basa esta guía.