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

Plugins

Un plugin es un paquete de Python que extiende starlette-admin mediante un único argumento de constructor. Un plugin puede agrupar cualquier combinación de campos, plantillas, recursos estáticos, convertidores de modelos, filtros, formatos de importación/exportación, backends de almacenamiento, suscriptores de eventos, vistas, rutas, middlewares, recursos de tema y catálogos de traducción.

Uso de un plugin

Pase los plugins a través del argumento plugins cuando construya su instancia de Admin:

from starlette_admin_geospatial import GeospatialPlugin
from starlette_admin.contrib.sqla import Admin

admin = Admin(engine, plugins=[GeospatialPlugin(default_zoom=13)])

El constructor del plugin recibe las opciones, y la lista se pasa directamente a Admin. No hay nada más que configurar ni registrar. Las opciones fluyen desde el constructor hasta el backend de Python, las plantillas Jinja y el JavaScript del frontend.

Creación de un plugin

Para escribir un plugin, parta de la plantilla oficial de cookiecutter. Esta genera un paquete publicable con la estructura de directorios y la configuración adecuadas.

Requisitos previos

Instale cookiecutter con su gestor de paquetes. Consulte la guía oficial de instalación para conocer los detalles:

pip install cookiecutter

Scaffolding

Ejecute la plantilla de cookiecutter desde cualquier ubicación:

cookiecutter gh:jowilf/starlette-admin --directory plugins/cookiecutter-starlette-admin-plugin

La plantilla le pedirá el nombre del plugin, el slug del paquete, la versión y algunas otras variables. Cuando termine, obtendrá un paquete autocontenido con:

  • Un directorio src/ que contiene la clase de su plugin y los campos.
  • Carpetas templates/, static/ y translations/ correctamente organizadas en namespaces.
  • Una suite de pruebas completa.
  • Una aplicación de ejemplo ejecutable.

La API de plugins

En el núcleo de cada plugin hay una subclase de BasePlugin (starlette_admin.plugins.BasePlugin), que le proporciona hooks para registrar sus funcionalidades mientras Admin se inicializa.

from starlette_admin.plugins import BasePlugin


class MyPlugin(BasePlugin):
    name = "my-plugin"

El atributo name es un identificador único en kebab-case que sirve además como namespace para sus plantillas y recursos estáticos. Cada plantilla y archivo estático que incluya su plugin debe encontrarse bajo plugins/<name>/.

Carpetas de assets

Un plugin puede contener exactamente tres carpetas en la raíz de su paquete. No hay nada que registrar, porque el admin las encuentra por convención:

  • templates/: plantillas Jinja, que deben situarse bajo templates/plugins/<name>/.
  • static/: recursos estáticos como archivos CSS y JS, que deben situarse bajo static/plugins/<name>/.
  • translations/: catálogos de traducción de Babel.

Mantenerse dentro del namespace plugins/<name>/ evita que sus assets colisionen con los archivos principales o con otros plugins, dejándolos además sobrescribibles mediante el propio templates_dir o static_dir del usuario.

Hooks declarativos

Sobrescriba los hooks declarativos para inyectar assets, registrar vistas o montar rutas.

  • css_links(self, request: Request) -> Sequence[str]: añade hojas de estilo al layout de todas las páginas del admin.
  • js_links(self, request: Request) -> Sequence[str]: añade scripts al layout de todas las páginas del admin.
  • views(self) -> Sequence[BaseView]: devuelve las vistas que se registrarán en la barra lateral del admin. Devuelva un DropDown para agruparlas.
  • routes(self) -> Sequence[Route | Mount]: devuelve endpoints sin interfaz montados bajo /plugins/<name>/, lo cual resulta práctico para webhooks y endpoints proxy.
  • middlewares(self) -> Sequence[Middleware]: añade middlewares de Starlette.
  • template_globals(self) -> dict[str, Any]: expone globals de Jinja, con el prefijo <name>_ para evitar colisiones.
  • template_filters(self) -> dict[str, Callable]: expone filtros de Jinja, con el prefijo <name>_ de la misma manera.

El hook de setup

setup(self, admin: BaseAdmin) -> None integra su plugin con los registros principales. Úselo para registrar convertidores de modelos, filtros, formatos de importación y exportación, backends de almacenamiento y suscriptores de eventos. Se ejecuta después de aplicar los hooks declarativos.

def setup(self, admin: "BaseAdmin") -> None:
    admin.events.subscribe(MyEventSubscriber(self.config))

El hook de ciclo de vida

on_mount(self, admin: BaseAdmin) -> None se ejecuta exactamente una vez, después de que la sub-aplicación de Starlette se haya construido y montado. La aplicación construida está disponible como admin.app.

Plantillas y overrides

Las plantillas de los plugins se incorporan automáticamente a la cadena de loaders. Un usuario puede sobrescribir una colocando un archivo en la ruta correspondiente dentro de su propio templates_dir, que siempre tiene prioridad. Para sobrescribir plugins/geospatial/fields/form/point.html, por ejemplo, debe crear templates_dir/plugins/geospatial/fields/form/point.html.

Para que un override del usuario pueda extender el original de forma segura, cada plugin dispone de un prefijo de mapeo @<name> que funciona igual que el prefijo @core. El override comienza con {% extends "@geospatial/fields/form/point.html" %} y extiende la plantilla base del plugin sin incluirse a sí mismo recursivamente.

Integración con el JavaScript del frontend

Un plugin que incluya campos personalizados debe empaquetar sus scripts del frontend conforme al contrato de inicializadores de campos. Así se garantiza que funcionen tanto en cargas completas de página como en fragmentos insertados dinámicamente.

  • Apunte localmente: realice las consultas dentro del elemento container que recibe, nunca sobre el document global.
  • Sea idempotente: el núcleo ejecuta el inicializador cuando el DOM está listo y de nuevo cada vez que inserta filas inline o fragmentos.
  • Use atributos data: lea la configuración desde los atributos data-* renderizados en el elemento del campo.
plugins/<name>/js/slider.js
(function () {
  function initSlider(container) {
    var input = container.querySelector('input[type="range"]');
    var output = container.querySelector(".sa-slider-output");
    var suffix = container.dataset.suffix || "";

    input.addEventListener("input", function () {
      output.textContent = input.value + suffix;
    });
  }

  // Register the initializer so core runs it on the right lifecycle events
  window.StarletteAdmin.registerFieldInitializer(function (element) {
    element.querySelectorAll("[data-sa-slider]").forEach(initSlider);
  });
})();

Puntos de extensión mediante el hook de setup

Los plugins utilizan los registros públicos existentes en lugar de una vía de extensión propia separada.

  • Converters: llame a register_converter, desde el backend contrib al que apunte, para mapear tipos de columnas ORM a sus clases de campos. Defina el campo en sí como una subclase ordinaria de StringField, almacenando y mostrando las geometrías como texto WKT:
from dataclasses import dataclass
from typing import Any

from starlette_admin.contrib.sqla.converters import register_converter
from starlette_admin.fields import StringField


@dataclass
class MyGeoField(StringField):
  ...


@register_converter("Geometry")
def convert_geometry(*args: Any, **kwargs: Any) -> MyGeoField:
    return MyGeoField(*args, **kwargs)
  • Filters: llame a register_filters para asociar clases de filtros a un tipo de campo.
from starlette_admin.contrib.sqla.filters import register_filters

register_filters(MyGeoField, WithinBoundingBoxFilter)
  • Storage: llame a register_storage para exponer un nuevo backend, como Azure o GCS.
from starlette_admin.storage import register_storage

register_storage(AzureBlobStorage())
  • Importers and Exporters: use register_import_format y register_export_format.
from starlette_admin.export import register_export_format

register_export_format("pdf", PDFExporter())

Un plugin puede dar soporte a varios backends ORM, así que impórtelos condicionalmente dentro de setup(). De este modo, el plugin sigue cargándose aunque el usuario solo haya instalado uno de ellos:

def setup(self, admin: "BaseAdmin") -> None:
    try:
        from starlette_admin_geospatial.contrib.sqla import register_sqla_converters

        register_sqla_converters()
    except ImportError:
        pass  # geoalchemy2 or sqlalchemy not installed

Próximos pasos

  • Temas personalizados: empaquete y comparta un sistema visual completo, usando el mismo flujo de trabajo de cookiecutter.
  • Eventos: la API de suscriptores que un plugin registra desde su hook setup().
  • Puntos de extensión: todos los registros y clases base en los que un plugin puede integrarse.