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.
Eventos
Un hook de método como before_create solo se ejecuta en la vista que lo define. El sistema de eventos permite que código externo a esa vista reaccione a lo que ocurre dentro de ella; esto significa que un registro de auditoría, un webhook o una invalidación de caché pueden vivir en un único lugar en lugar de copiarse y pegarse en cada ModelView que usted escriba.
from starlette_admin.events import AdminEvent, AfterCreateContext
async def notify_slack(ctx: AfterCreateContext) -> None:
print(f"New {ctx.view_key} created: pk={ctx.pk}")
admin.events.on(AdminEvent.AFTER_CREATE, notify_slack)
Registre esto una sola vez junto a su instancia de admin y el endpoint de creación de cada vista lo llamará, incluidas las vistas que añada más adelante.
Nivel de vista frente a nivel de admin
Cada vista tiene un atributo events al que puede suscribirse directamente, con alcance limitado únicamente a esa vista. La instancia de Admin también tiene uno, que alcanza a todas las vistas registradas en ella, o a un subconjunto si pasa keys=.
view.events.on(...): Se dispara solo para esa vista.admin.events.on(...): Se dispara para todas las vistas actuales y futuras, salvo que lo restrinja conkeys=.
Puede registrarse en admin.events antes o después de llamar a admin.add_view(...). El orden no importa: un handler registrado primero seguirá adjuntándose a la vista cuando la añada.
Hooks de método frente a suscripciones de eventos
Ambos se disparan en el mismo punto del ciclo de vida de la petición. Se diferencian en dónde reside el código y a cuántas vistas alcanza.
| Característica | Hook de método (before_create, ...) |
Suscripción de evento (view.events / admin.events) |
|---|---|---|
| Dónde reside el código | Dentro de la clase de la vista | En cualquier lugar, por ejemplo una función a nivel de módulo o una clase suscriptora |
| Alcance | Esa vista concreta | Una vista (view.events) o todas las vistas (admin.events) |
| Adecuado para | Lógica específica de ese recurso (generar un slug para un título, estampar una marca de tiempo) | Preocupaciones transversales (registros de auditoría, notificaciones, plugins) |
| ¿Se permiten varios? | No, un método por vista | Sí, cualquier cantidad de handlers por evento, ordenados por prioridad |
Use un hook de método cuando la lógica sea intrínseca al modelo. Use una suscripción de evento cuando no pertenezca a ninguna vista en particular, o cuando vaya a distribuirlo como una pieza reutilizable entre varios admins.
from typing import Any
from starlette.requests import Request
from starlette_admin.contrib.sqla import ModelView
class PostView(ModelView):
# Belongs to this view only, stays here
async def before_create(
self, request: Request, data: dict[str, Any], obj: Any
) -> None:
obj.slug = data["title"].lower().replace(" ", "-")
Valores de AdminEvent
AdminEvent es un enum de cadenas. Estos son los miembros emitidos activamente por el ciclo de vida de las vistas:
| Evento | Cuándo se dispara | Clase de contexto |
|---|---|---|
BEFORE_CREATE / AFTER_CREATE |
Registro creado | BeforeCreateContext / AfterCreateContext |
AFTER_CREATE_COMMITTED |
Transacción de creación confirmada | AfterCreateContext |
BEFORE_EDIT / AFTER_EDIT |
Registro actualizado | BeforeEditContext / AfterEditContext |
AFTER_EDIT_COMMITTED |
Transacción de edición confirmada | AfterEditContext |
BEFORE_DELETE / AFTER_DELETE |
Registro eliminado | BeforeDeleteContext / AfterDeleteContext |
AFTER_DELETE_COMMITTED |
Transacción de eliminación confirmada | AfterDeleteContext |
BEFORE_ACTION / AFTER_ACTION |
Acción por lotes o por fila ejecutada | BeforeActionContext / AfterActionContext |
BEFORE_EXPORT / AFTER_EXPORT |
Exportación iniciada | BeforeExportContext / AfterExportContext |
BEFORE_IMPORT / AFTER_IMPORT |
Importación iniciada | BeforeImportContext / AfterImportContext |
AFTER_LOGIN |
Inicio de sesión exitoso | AfterLoginContext |
AFTER_CREATE_COMMITTED, AFTER_EDIT_COMMITTED y AFTER_DELETE_COMMITTED solo se disparan en backends que difieren el commit hasta el final de la petición, lo cual hoy significa el backend de SQLAlchemy. Consulte Views para conocer los métodos de hook after_create_committed, after_edit_committed y after_delete_committed que los emiten.
Para AFTER_DELETE_COMMITTED, ctx.obj es una instancia detached: sus atributos ya cargados siguen siendo legibles, pero leer un atributo que no se cargó antes de la eliminación genera un error, porque la fila que lo respalda ya no existe.
Cada contexto es una dataclass que hereda de EventContext, la cual contiene campos comunes a todos los eventos:
| Atributo | Tipo | Descripción |
|---|---|---|
event |
AdminEvent o str |
El evento que se disparó |
request |
Request |
La petición en curso |
view_key |
str |
El key de la vista |
extra |
dict |
Vacío por defecto, disponible para que usted almacene datos en una cadena de handlers personalizada |
Cada subclase añade los campos relevantes para su evento.
Los eventos de edición disparados por una edición en línea desde la página de lista incluyen extra["inline"] = True, y sus payloads data / old_data contienen únicamente el campo editado. Todo lo demás es idéntico a una edición normal, por lo que los handlers existentes no requieren cambios.
Suscripción mediante decorador
view.events.on() funciona tanto como decorador como llamada directa de función:
import logging
from starlette_admin.events import AdminEvent, BeforeDeleteContext
from starlette_admin.contrib.sqla import ModelView
logger = logging.getLogger(__name__)
class OrderView(ModelView):
fields = ["id", "customer_name", "total", "status"]
order_view = OrderView(Order, icon="fa fa-shopping-cart")
@order_view.events.on(AdminEvent.BEFORE_DELETE)
async def log_deletion(ctx: BeforeDeleteContext) -> None:
logger.info("Deleting order pk=%s", ctx.pk)
Registrado de esta manera, log_deletion se dispara únicamente para order_view, no para otras vistas del admin. El método on() también acepta el handler directamente, sin la forma de decorador:
AdminEventSubscriber: agrupación de handlers
Cuando una misma preocupación reacciona a varios eventos, AdminEventSubscriber los mantiene en una única clase en lugar de dispersar funciones a nivel de módulo. Decore los métodos con @on(AdminEvent.X), el on a nivel de módulo de starlette_admin.events y no el método del bus, y luego llame a subscribe() una vez:
import logging
from starlette_admin.events import (
AdminEvent,
AdminEventSubscriber,
AfterCreateContext,
AfterDeleteContext,
AfterEditContext,
on,
)
logger = logging.getLogger(__name__)
class AuditSubscriber(AdminEventSubscriber):
"""Logs every create, update, or delete, on any view."""
@on(AdminEvent.AFTER_CREATE)
async def record_create(self, ctx: AfterCreateContext) -> None:
logger.info("created %s pk=%s", ctx.view_key, ctx.pk)
@on(AdminEvent.AFTER_EDIT)
async def record_update(self, ctx: AfterEditContext) -> None:
logger.info("updated %s pk=%s", ctx.view_key, ctx.pk)
@on(AdminEvent.AFTER_DELETE)
async def record_delete(self, ctx: AfterDeleteContext) -> None:
logger.info("deleted %s pk=%s", ctx.view_key, ctx.pk)
admin.events.subscribe(AuditSubscriber())
subscribe() está disponible tanto en view.events como en admin.events. Llámelo sobre view.events para limitar el alcance del suscriptor a una sola vista.
Un mismo método puede manejar varios eventos: @on(AdminEvent.AFTER_CREATE, AdminEvent.AFTER_EDIT) registra el mismo método para ambos.
admin.events: delegación hacia las vistas
admin.events.on() acepta los mismos argumentos que view.events.on(), además de keys=, una lista de claves de vista para restringir la suscripción. Si lo deja sin establecer (None, el valor predeterminado), todas las vistas de modelo actuales y futuras recibirán el handler:
import httpx
from starlette_admin.events import AdminEvent, AfterCreateContext
@admin.events.on(AdminEvent.AFTER_CREATE, keys=["order"])
async def notify_new_order(ctx: AfterCreateContext) -> None:
async with httpx.AsyncClient() as client:
await client.post(SLACK_WEBHOOK_URL, json={"text": f"New order: {ctx.pk}"})
Solo la vista registrada con key="order", o cuya clave predeterminada resuelve a "order", llama a este handler. Un AFTER_CREATE en cualquier otra vista no lo activará.
admin.events.subscribe() también acepta keys=, de modo que puede limitar el alcance de un AdminEventSubscriber a un subconjunto de vistas de la misma manera:
keys= solo afecta a los eventos del ciclo de vida de las vistas de la tabla anterior: creación, edición, eliminación, acción, exportación e importación. Así es como admin.events decide a qué vistas aplica un handler. AFTER_LOGIN es de nivel de admin y no está vinculado a ninguna vista, por lo que keys= no tiene efecto sobre él.
Prioridad
on() acepta un argumento de palabra clave priority, un entero cuyo valor predeterminado es 0. Los handlers de un mismo evento se ejecutan en orden descendente de prioridad, de modo que un número mayor se dispara primero:
import logging
from starlette_admin.events import AdminEvent, BeforeDeleteContext
logger = logging.getLogger(__name__)
@order_view.events.on(AdminEvent.BEFORE_DELETE, priority=10)
async def validate_can_delete(ctx: BeforeDeleteContext) -> None:
if ctx.obj.status == "shipped":
raise ValueError("Cannot delete a shipped order") # runs first
@order_view.events.on(AdminEvent.BEFORE_DELETE, priority=0)
async def log_deletion(ctx: BeforeDeleteContext) -> None:
logger.info("deleting order pk=%s", ctx.pk) # runs second
Los handlers con la misma prioridad se ejecutan en orden de registro. Los métodos de AdminEventSubscriber reciben una prioridad mediante @on(AdminEvent.X, priority=10), que se reenvía de la misma manera.
Warning
Un handler de BEFORE_DELETE, o cualquier handler BEFORE_*, que lance una excepción detiene la operación, y los handlers posteriores de ese evento no se ejecutan. Un handler AFTER_* que lance una excepción convierte un cambio ya confirmado en una petición fallida. Si un fallo no debe manifestarse como un error del admin, envuelva la lógica riesgosa, como llamadas de red o APIs de terceros, en su propio bloque try/except dentro del handler.
Ejemplo ampliado
examples/05-events ejecuta juntos todos los patrones de esta página: overrides de hooks en PostView, un AuditSubscriber registrado en admin.events para todas las vistas, registro directo de handlers para advertencias de eliminación, exportación e importación, un handler con alcance limitado a post_view.events y un CommentModerationSubscriber con alcance limitado a comment_view.events. Ejecútelo para observar cómo interactúan la prioridad y el alcance en una sola aplicación.
¿Qué sigue?
- Views: Los hooks de método
before_*yafter_*sobre los que se basa esta página. - Actions: Acciones por lotes y por fila, que emiten
BEFORE_ACTION/AFTER_ACTION. - Inline Forms: Registros anidados creados junto a un elemento padre.