Die Administrationsoberfläche
Rustango erzeugt eine vollständige Admin-Oberfläche aus deinen Modellen — ein
fertiges Backoffice zum Durchsuchen und Bearbeiten deiner Daten, aber mit
null Boilerplate pro Modell. Füge #[derive(Model)] hinzu, binde das Admin einmal
ein, und jedes Modell erhält eine Listenansicht mit Suche, Filtern, Sortierung,
Paginierung und Massenaktionen; ein nach Feldgruppen gegliedertes Erstellen-/Bearbeiten-
Formular; inline-Bearbeitung von Kindobjekten; einen Prüfpfad pro Zeile; und eine
Live-Modellreferenz. Alles Folgende wird deklarativ in einem admin(...)-Block am
Derive konfiguriert, oder mit einer Handvoll Builder-Methoden und Registrierungsmakros
auf Modulebene.
Quelle:
rustango::admin(dieadmin(...)-Derive-Optionen, dieBuilder- API und die Registrierungsmakros) — hinter demadmin-Feature (standardmäßig aktiviert).Lauffähige Version: jedes Feature auf dieser Seite wird in einem getesteten, kompilierbaren Beispiel unter
crates/rustango/examples/admin_demoausgeführt. Die Screenshots auf dieser Seite stammen aus diesem Beispiel. Wenn ein Snippet seltsam aussieht, vergleiche es damit.
Ein Begriff hier ist neu für dich? model, fieldset, audit trail — siehe das Glossar.
Inhaltsverzeichnis
- Einbinden · Die Startseite
- Ein Modell konfigurieren: der
admin(...)-Block - Die Listenansicht — Spalten, Suche, Filter, Datumshierarchie, Sortierung, Paginierung
- Das Änderungsformular — Feldgruppen, Widgets, FK-Bearbeitung, vorbefüllte & schreibgeschützte Felder
- Inlines · Massenaktionen · Prüfpfad
- Berechnete Spalten & benutzerdefinierte Filter
- Benutzerdefinierte Views, Querysets & Berechtigungen
- Authentifizierung · Theming & Branding
Builder-Referenz · Routen-Referenz- Die Modellreferenz (
__docs) · Das Beispiel ausprobieren
Einbinden
Das Admin ist standardmäßig offen. Es erkennt und liefert jedes Modell automatisch — auflisten, erstellen, bearbeiten, löschen — ohne Authentifizierung, bis du sie hinzufügst. Mache es nicht öffentlich zugänglich, bevor du das Login verdrahtet hast: siehe Authentifizierung weiter unten.
Das Admin ist ein axum::Router, den du aus einem Datenbank-Pool baust und unter
einen Pfad einhängst:
use rustango::admin;
let admin_router = admin::Builder::new(pool.clone())
.title("Admin Demo")
.subtitle("rustango auto-admin showcase")
.admin_prefix("/admin") // MUST match the nest path below
.build();
let api = axum::Router::new().nest("/admin", admin_router);
Das Auto-Admin erkennt jedes #[derive(Model)] in deiner Binärdatei automatisch
über die inventory-Registry — du registrierst Modelle nicht einzeln. Öffne
http://localhost:8080/admin, und sie erscheinen gruppiert in der Seitenleiste.
admin_prefixmuss dem Einhängepfad entsprechen. Das Admin baut seine Links und Formularaktionen ausadmin_prefix(Standard/__admin). Wenn du unter/admineinhängst, aber den Standard-Präfix belässt, liefert jeder Link einen 404. Setze sie gleich.
Die Registry einbinden. Die Inventory-Registrierung wird nur dann in die finale Binärdatei eingebunden, wenn die Modelltypen irgendwo referenziert werden. Ein Bibliotheks-Crate, dessen Modelle nicht anderweitig verwendet werden, benötigt eventuell einen Anstoß per
let _ = std::any::type_name::<Post>();inmain(das Beispiel macht das) — andernfalls verwirft der Linker sie und sie erscheinen nie.
Die Startseite
Die Admin-Wurzel (GET /<prefix>) listet jedes registrierte Modell auf, gruppiert nach
App, mit Tabellennamen und Feldanzahl jedes Modells — plus einem Recent actions-
Feed der neuesten geprüften Änderungen.
Ein Modell konfigurieren: der admin(...)-Block
Alles darüber, wie ein Modell erscheint, wird in einem admin(...)-Block am Derive
festgelegt. Hier der Vorzeige-Post aus dem Beispiel, der fast jeden Stellhebel
ausübt:
#[derive(Model, Clone, Debug)]
#[rustango(
table = "posts",
display = "title",
admin(
list_display = "id, title, author_id, status, view_count, published_at",
list_display_links = "id, title",
list_filter = "status, author_id",
search_fields = "title, body",
search_help_text = "Search posts by title or body",
ordering = "-published_at",
list_per_page = 10,
date_hierarchy = "published_at",
fieldsets = "Content: title, body, status | Publishing: author_id, published_at, view_count",
actions = "publish, archive",
),
audit(track = "title, body, status"),
)]
pub struct Post { /* … */ }
display = "title" (am Modell, außerhalb von admin(...)) legt das menschenlesbare
Label fest, das überall verwendet wird, wo eine Zeile referenziert wird — FK-Spalten in
den Listen anderer Modelle, der Breadcrumb, der Titel der Detailseite.
Jede admin(...)-Option
| Schlüssel | Beispiel | Was es bewirkt |
|---|---|---|
list_display | "id, title, status" | In der Liste angezeigte Spalten, in Reihenfolge. FK-Spalten rendern den display-Wert des Ziels. Berechnete Spalten (siehe unten) können hier benannt werden. Leer = jedes skalare Feld. |
list_display_links | "id, title" | Welche list_display-Zellen zur Detailseite verlinken. Muss eine Teilmenge von list_display sein. |
list_filter | "status, author_id" | Facetten-Karten in der rechten Leiste — eindeutige Werte + Anzahlen, zum Filtern anklicken. Funktioniert bei skalaren und FK-Spalten. |
search_fields | "title, body" | Felder, die das ?q=-Suchfeld abgleicht (Groß-/Kleinschreibung-unabhängig ILIKE/LIKE). |
search_help_text | "Search by title" | Beschriftung, die neben dem Suchfeld gerendert wird. |
ordering | "-published_at, id" | Standardsortierung. --Präfix = DESC; ohne = ASC. Mehrere Schlüssel durch Komma getrennt. |
list_per_page | 10 | Seitengröße (Standard 50). |
date_hierarchy | "published_at" | Aufschlüsselungsleiste Jahr → Monat → Tag über der Liste, auf einer Date-/DateTime-Spalte. |
fieldsets | "Content: title, body | Meta: status" | Gliedert das Änderungsformular in benannte Abschnitte. Pipe | trennt Abschnitte, Komma trennt Felder; die Title:-Legende ist optional. |
actions | "publish, archive" | Massenaktionen, die im Aktionsauswähler der Liste angeboten werden (jede benötigt einen registrierten Handler — siehe Massenaktionen). |
readonly_fields | "created_at" | Felder, die im Änderungsformular als Text (ohne Eingabe) gerendert werden. |
raw_id_fields | "author_id" | FK-Felder, die über eine reine ID-Eingabe + Nachschlage-Link bearbeitet werden (gut für große Zieltabellen). |
autocomplete_fields | "author_id" | FK-Felder, die über eine Ajax-Vervollständigung bearbeitet werden, gestützt auf den __autocomplete-Endpunkt des Ziels. |
prepopulated_fields | "slug:title" | Ein Feld automatisch befüllen, indem ein anderes beim Tippen sluggifiziert wird (target:source; kombiniere Quellen mit +). |
list_select_related | "all" / "none" / "author_id" | Steuert das automatische JOIN von FK-Spalten in der Listenabfrage. "all" (Standard) joint jeden FK; "none" deaktiviert; ein CSV beschränkt auf benannte FKs. |
formfield_overrides | "status:textarea" | Überschreibt das Formular-Widget eines Feldes (field:widget) — siehe die Widget-Tabelle. |
actions_on_top | true | Rendert die Massenaktionsleiste über der Liste (Standard true). |
actions_on_bottom | false | Rendert eine zweite Aktionsleiste unter der Liste (Standard false). |
Die Listenansicht
GET /<prefix>/<table> rendert die Liste. Aus dem einzelnen admin(...)-Block oben
erhältst du sortierbare Spalten, ein Suchfeld mit Hilfetext, die Status-/Autor-Facetten-
Karten mit Live-Anzahlen, die Datumsaufschlüsselung, Paginierung mit 10/Seite und den
publish/archive-Aktionsauswähler.
Filtern. Klicke auf einen beliebigen Wert in einer list_filter-Facetten-Karte, um
die Liste einzuschränken; der aktive Filter erscheint als Chip mit einem clear-Link,
und Zeilenanzahl sowie Facetten-Anzahlen aktualisieren sich. Filter, Suche, Sortierung
und die Datumshierarchie fügen sich alle in der Query-Zeichenkette zusammen und lassen
sich kombinieren.
Sortieren. Klicke auf einen Spaltenkopf zum Sortieren; erneut klicken, um die
Richtung umzukehren (?sort=col&order=asc|desc). Der Standard kommt aus ordering.
Paginierung. list_per_page legt die Seitengröße fest; navigiere mit ?page=N.
Registriere sehr große Tabellen mit Builder::skip_count_for([...]), um das
SELECT COUNT(*) zu überspringen (der Pager zeigt dann "Page N" ohne Gesamtsumme); ein
?count=skip pro Anfrage bewirkt dasselbe ad hoc.
Suche. Wenn search_fields gesetzt ist, erscheint ein Suchfeld und gleicht diese
Felder mit ILIKE (PostgreSQL) / LIKE (MySQL, SQLite) ab. search_help_text wird als
dessen Beschriftung gerendert.
Datumshierarchie. Mit gesetztem date_hierarchy sitzt ein Breadcrumb Jahr → Monat →
Tag über der Tabelle; das Hineinnavigieren fügt halboffene Bereichsfilter auf dieser
Spalte hinzu, unter Verwendung der tri-dialektalen Datumsextraktion (PostgreSQL
EXTRACT, MySQL, SQLite strftime).
Das Änderungsformular
GET /<prefix>/<table>/new (erstellen) und GET /<prefix>/<table>/<pk>/edit
(bearbeiten) rendern das Formular. fieldsets gruppiert die Eingaben in benannte
Abschnitte; ohne es erscheinen alle bearbeitbaren Felder in einem Block.
Das Absenden eines Formulars validiert die Eingabe, schreibt die Zeile, erfasst einen
Prüfpfad-Eintrag und leitet zur schreibgeschützten Detail-Ansicht
(GET /<prefix>/<table>/<pk>) weiter, die jedes Feld plus Inlines und die Prüfpfad-Karte
(unten) zeigt. Die Edit- und Delete-Schaltflächen der Detailseite führen zum
Formular und zur Löschbestätigung.
Formular-Widgets
Jedes Feld rendert standardmäßig eine seinem Typ entsprechende Eingabe —
<input type="number"> für Ganzzahlen, type="date"/datetime-local für Datumsangaben,
type="checkbox" für Booleans, ein <textarea> für lange Zeichenketten, ein <select>
für FK-Spalten und so weiter. Überschreibe pro Feld mit
formfield_overrides = "field:widget":
| Widget | Gilt für | Rendert |
|---|---|---|
textarea | String | mehrzeiliges <textarea> |
password | String | <input type="password"> |
email | String | <input type="email"> |
url | String | <input type="url"> |
color | String | <input type="color"> |
slug | String | Texteingabe mit Slug-Muster |
ipaddress | String | Texteingabe mit IP-Muster |
json | Json | monospaced <textarea> |
hidden | beliebig | <input type="hidden"> |
Fremdschlüssel bearbeiten
FK-Spalten haben drei Bearbeitungsmodi:
- Standard — ein
<select>, befüllt aus der Zieltabelle, das dendisplay-Wert jeder Zeile anzeigt. raw_id_fields— eine reine ID-Eingabe plus ein Nachschlage-Link; am besten, wenn die Zieltabelle zu groß ist, um sie in einem Dropdown aufzuzählen.autocomplete_fields— eine Ajax-Vervollständigung, die diesearch_fieldsdes Zielmodells überGET /<prefix>/<target>/__autocomplete?q=…abfragt.
Vorbefüllte & schreibgeschützte Felder
prepopulated_fields = "slug:title" gibt clientseitiges JS aus, das das Quellfeld beim
Tippen in das Zielfeld sluggifiziert (kombiniere mehrere Quellen mit +, z. B.
"slug:section+title"). readonly_fields rendert die benannten Felder als maskierten
Text im Formular statt als Eingaben.
Inlines
Inlines zeigen die Zeilen eines Kindmodells direkt auf der Seite des Elternobjekts, sodass beides in einem Formular bearbeitet wird. Registriere eines auf Modulebene:
rustango::register_admin_inline!(
parent = "posts",
child = "comments",
fk = "post_id", // child column → parent PK
kind = rustango::admin::inlines::InlineKind::Tabular, // or Stacked
label = "Comments",
fields = &["author_name", "body", "created_at"],
);
Auf der Detail-Seite des Elternobjekts rendern die Kinder als schreibgeschützte
Tabelle; auf der Edit-Seite werden sie zu einem bearbeitbaren FormSet (Zeilen
hinzufügen / ändern / löschen an Ort und Stelle). Optionen: kind (Tabular — eine
Tabellenzeile pro Kind, oder Stacked — eine Feldgruppe pro Kind), label, fields
(Standard: jedes Skalar außer dem FK), extra (leere Zeilen zum Hinzufügen angeboten),
max_num und readonly_fields.
Für Kindzeilen, die über einen generischen Fremdschlüssel (Paar aus Content-Type +
Objekt-PK) statt über eine einzelne FK-Spalte angehängt sind, verwende
register_admin_inline_generic!(parent, child, ct = "content_type_id", pk = "object_pk", …) — ansonsten dieselben Optionen.
Massenaktionen
Benenne die Aktionen in admin(actions = "...") und registriere dann einen Handler pro
Aktion am Builder. Der Handler erhält den Pool und die Primärschlüssel der
ausgewählten Zeilen:
use rustango::core::SqlValue;
let admin_router = admin::Builder::new(pool)
.register_action("posts", "publish", |pool, pks| {
Box::pin(async move {
let ids: Vec<String> = pks.iter().filter_map(|v| match v {
SqlValue::I64(n) => Some(n.to_string()),
SqlValue::I32(n) => Some(n.to_string()),
_ => None,
}).collect();
if !ids.is_empty() {
let sql = format!("UPDATE posts SET status='published' WHERE id IN ({})", ids.join(","));
rustango::sql::raw_execute_pool(pool, &sql, Vec::new()).await?;
}
Ok(())
})
})
.register_action("posts", "archive", /* … */)
.build();
Wähle Zeilen mit den Kontrollkästchen aus, wähle die Aktion im Auswähler und sende ab
(POST /<prefix>/<table>/__action). delete_selected ist eingebaut — du registrierst es
nicht. Ein in admin(actions = ...) gelisteter Aktionsname ohne registrierten Handler
erscheint schlicht nicht.
Prüfpfad
Füge audit(track = "field1, field2") zu einem Modell hinzu, und jedes Erstellen,
Aktualisieren und Löschen wird in der Tabelle rustango_audit_log erfasst (für dich
erstellt, wenn du migrate ausführst). Nur Modelle mit einem audit(...)-Attribut
werden protokolliert; track wählt aus, welche Felder im Diff erfasst werden (lasse es
weg, um alle Skalare zu verfolgen).
#[rustango(table = "posts", audit(track = "title, body, status"))]
Jeder Eintrag speichert die Tabelle, den Primärschlüssel, die Operation, die Quelle, ein
Diff pro Feld ({before, after}) als JSON, den Akteur, einen Zeitstempel und einen
manipulationssicheren Fingerabdruck. Zwei Stellen zeigen es an:
- Die Detailseite des Modells erhält eine Audit trail-Karte, die aktuelle Änderungen auflistet (wer, wann und das Diff), mit einem View full history-Link (im Detail-Screenshot oben gezeigt).
- Die Activity-Ansicht der Seitenleiste (
GET /<prefix>/__audit) ist ein zeilenübergreifender Feed, neueste zuerst, mit Facetten-Karten für Entität / Operation / Quelle und einem Aufräumformular, um Einträge älter als N Tage zu bereinigen (was selbst als Prüfpfad-Eintrag erfasst wird).
Berechnete Spalten und benutzerdefinierte Filter
Wenn der deklarative Block nicht ausreicht, erweitern zwei Makros auf Modulebene die Listenansicht:
Berechnete Spalten — eine abgeleitete Spalte, nicht aus der Datenbank:
rustango::register_admin_computed!(
"posts", "word_count", "Words",
|row| row.get("body").and_then(|v| v.as_str())
.unwrap_or_default().split_whitespace().count().to_string(),
);
// then add `word_count` to admin(list_display = "...").
Der Closure erhält die Zeile als serde_json::Value und gibt vorab maskiertes HTML
zurück. Eine Form mit 5 Argumenten fügt link = |row| Option<String> hinzu, um die
Zelle in ein <a> einzuschließen.
Benutzerdefinierte Listenfilter — Filterlogik, die die Auto-Facetten nicht ausdrücken können:
fn by_status(value: &str) -> Vec<rustango::core::Filter> { /* map value → predicates */ }
rustango::register_admin_list_filter!(
"posts", "status", "Status",
&[("draft", "Drafts"), ("published", "Published")], // (value, label) choices
by_status, // fn(&str) -> Vec<Filter>
);
Benutzerdefinierte Views, Querysets und Berechtigungen
Drei weitere Registrierungsmakros hängen sich in das Verhalten eines registrierten Modells ein:
- Benutzerdefinierte Admin-Seiten —
register_admin_view!("posts", "duplicate", Method::POST, "Duplicate", handler)bindet eine zusätzliche Seite/Aktion unter/<prefix>/posts/duplicateein. Der Handler ist ein asynchronesfn(Pool, Request) -> Response. (Reservierte Suffixe wienew,__action,__autocomplete,{pk},{pk}/edit,{pk}/deletewerden mit einer Warnung übersprungen.) - Queryset-Einschränkung —
register_admin_queryset!("posts", hook), wobeihook: fn(&Parts) -> Vec<Filter>einschränkt, was eine Anfrage sehen kann (z. B. nur die Zeilen des aktuellen Benutzers). Mehrere Hooks auf einer Tabelle fügen sich zusammen. - Berechtigungen auf Zeilenebene —
register_admin_object_permission!("posts", "change", check), wobeicheck: fn(&Parts, Option<&Value>) -> boolpro Zeile erlaubt oder verweigert. Eingebaute Handler konsultieren die Aktionenadd,change,deleteundview; mehrere Hooks werden mit UND verknüpft.
Für gröbere, codename-basierte Zugriffskontrolle sperrt Builder::with_user_perms([...])
jede Tabelle auf {table}.view / .add / .change / .delete: fehlendes view
verbirgt das Modell und liefert bei direkten Treffern 404, fehlendes change rendert es
schreibgeschützt, und fehlendes add / delete entfernt diese Schaltflächen.
Authentifizierung
Standardmäßig ist das Admin offen — jeder, der es erreichen kann, kann es benutzen. Sperre es auf eine von zwei Arten:
-
Session-Auth (eingebaut).
Builder::with_session_auth(secret)bindet/login+/logout(und eine optionale/account/password-Änderungsseite) ein und umgibt jede andere Route mit Middleware, die anonyme Anfragen auf das Login-Formular umleitet. Anmeldedaten liegen in der Tabellerustango_admin_users(username, argon2password_hash,is_superuser,active,created_at); das Ändern eines Passworts widerruft die anderen Sitzungen dieses Benutzers. Optionale TOTP-Zweifaktor-Authentifizierung ist hinter demtotp-Feature verfügbar, mit Registrierung unter/account/totp.let admin = admin::Builder::new(pool) .with_session_auth(session_secret) .secure_cookies(true) // HTTPS-only cookie in production .build(); -
Setze deine eigene Auth davor. Lasse das Admin offen und setze HTTP-Basic-Auth, OAuth2 oder unternehmensweites SSO mit deiner eigenen Middleware vor den Einhängepfad.
Wenn die Session-Auth aktiv ist, zeigt die Fußzeile der Seitenleiste eine Zeile
"Signed in as username" und eine Logout-Schaltfläche (ein POST-Formular).
Eigenständige Admins posten standardmäßig an {admin_prefix}/logout; ein Mandanten-Admin
sitzt hinter der eigenen Logout-Route der Mandanten-Schicht, also verweise die
Schaltfläche dorthin mit Builder::logout_url:
let admin = admin::Builder::new(pool)
.with_session_auth(session_secret)
.logout_url("/staff-logout") // POST target for the sidebar Logout button
.build();
Der Mandanten-Admin-Builder verdrahtet dies automatisch mit seiner
RouteConfig::logout_url, sodass die Schaltfläche immer eine Route trifft, die
existiert.
Theming und Branding
| Methode | Wirkung |
|---|---|
.theme_mode("light" | "dark" | "auto") | Standard-Farbthema (setzt data-theme auf <html>). |
.title(s) / .subtitle(s) | Kopftext der Seitenleiste. |
.brand_logo_url(url) | Über dem Titel gerendertes Logo. |
.brand_name(s) / .brand_tagline(s) | Mandantenspezifische Überschreibungen von Titel/Untertitel. |
.tenant_brand_css(css) | Ein vorgefertigter :root{…}-CSS-Variablen-Block, inline eingebettet für mandantenspezifische Paletten. |
.from_settings(pool, &settings) | Baut Branding + Sichtbarkeit aus den Abschnitten [admin] / [brand] deiner Konfigurationsdatei. |
from_settings liest admin.title, admin.subtitle, admin.logo_url,
admin.theme_mode, admin.url_prefix, admin.allowed_tables,
admin.read_only_tables, greift auf den [brand]-Abschnitt zurück und setzt
secure_cookies standardmäßig auf true. Imperative Builder-Aufrufe danach gewinnen
weiterhin.
Builder-Referenz
Jede Methode auf admin::Builder (jede gibt Self zur Verkettung zurück, sofern nicht
anders angegeben):
| Methode | Zweck |
|---|---|
new(pool) | Aus einem beliebigen Pool konstruieren (PostgreSQL / MySQL / SQLite). Standardwerte: Präfix /__admin, Dev-Cookies. |
from_settings(pool, &settings) | Aus geparster Konfiguration konstruieren (config-Feature). |
title(s) / subtitle(s) | Kopf- / Unterüberschrift der Seitenleiste. |
admin_prefix(p) | URL-Präfix — muss dem Einhängepfad entsprechen. Standard /__admin. |
audit_url(u) | Pfad der Aktivitäts-/Prüfpfad-Ansicht. Standard /__audit. |
static_url(u) | Präfix für eingebettete Assets (Favicon, Logo). Standard /__static__. |
change_password_url(u) | Pfad der Selbstbedienungs-Passwortänderungsseite (fügt den Seitenleisten-Link hinzu). |
show_only([tables]) | Whitelist, welche Tabellen erscheinen; andere liefern 404 und sind verborgen. |
read_only([tables]) | Diese Tabellen rendern, aber Erstellen/Bearbeiten/Löschen verbieten. |
read_only_all() | Markiert jede Tabelle als schreibgeschützt. |
skip_count_for([tables]) | Überspringt COUNT(*) auf riesigen Tabellen (Pager zeigt "Page N"). |
with_user_perms([codenames]) | Sperrt Tabellen auf {table}.view/add/change/delete. |
register_action(table, name, handler) | Registriert einen Massenaktions-Handler. |
with_session_auth(secret) | Erfordert Cookie-Login (/login + /logout). |
logout_url(u) | POST-Ziel für die Logout-Schaltfläche der Seitenleiste. Standard {admin_prefix}/logout; Mandanten-Admins setzen es auf ihre Mandanten-Logout-Route. |
secure_cookies(bool) | Setzt das Secure-Flag (HTTPS-only) auf dem Session-Cookie. |
theme_mode(m) | "light" / "dark" / "auto". |
brand_logo_url(url) | Logo über dem Titel. |
brand_name(s) / brand_tagline(s) | Mandantenspezifische Marken-Überschreibungen. |
tenant_brand_css(css) | Mandantenspezifischer CSS-Variablen-Block. |
impersonated_by(operator_id) | Rendert ein Impersonations-Banner (Operator-Konsole). |
tenant_mode() | Verbirgt registry-scoped Modelle (automatisch für Mandanten-Admins gesetzt). |
build() | Finalisiert und gibt den axum::Router zurück. |
Routen-Referenz
Alle Pfade sind relativ zu admin_prefix:
| Pfad | Methode | Was |
|---|---|---|
/ | GET | Startseite — Modellindex + aktuelle Aktionen. |
/<table> | GET | Listenansicht (Suche, Filter, Sortierung, Paginierung). |
/<table> | POST | Erstellen-Absenden. |
/<table>/new | GET | Erstellungsformular. |
/<table>/<pk> | GET | Detailansicht (schreibgeschützt), mit Inlines + Prüfpfad-Karte. |
/<table>/<pk> | POST | Aktualisieren-Absenden. |
/<table>/<pk>/edit | GET | Bearbeitungsformular. |
/<table>/<pk>/delete | POST | Löschen (nach Bestätigung). |
/<table>/__action | POST | Führt eine Massenaktion auf ausgewählten PKs aus. |
/<table>/__autocomplete | GET | FK-Vervollständigungs-JSON (?q=). |
/__docs | GET | Modellreferenz. |
/__audit (oder audit_url) | GET | Aktivitäts-Feed + Aufräumen. |
/login, /logout | GET/POST | Session-Auth (wenn aktiviert). |
/account/password, /account/totp | GET/POST | Selbstbedienungs-Passwortänderung / TOTP-Registrierung. |
Mit register_admin_view! registrierte benutzerdefinierte Routen binden unter
/<table>/<suffix> ein.
Die Modellreferenz
Jedes Admin liefert eine Live-Modellreferenz unter
<prefix>/__docs — ein schreibgeschützter Katalog jedes registrierten Modells mit seinen
Feldern, Spalten, Typen, Flags (PK, unique, …) und Beziehungen. Nichts zu konfigurieren;
es wird aus deinen Modellen generiert, also weicht es nie vom Schema ab.
Das Beispiel ausprobieren
cd crates/rustango/examples/admin_demo
export DATABASE_URL=postgres://rustango:rustango@localhost:5432/admin_demo
cargo run -- migrate # tables + the audit-log table
cargo run # seeds demo data, serves the admin at /admin
Öffne dann http://localhost:8080/admin und klicke in Posts hinein, um die Filter, Suche, Datumshierarchie, Aktionen, Feldgruppen, Inline-Kommentare, den Prüfpfad und die Modellreferenz zu sehen — jeden Screenshot auf dieser Seite — an einem Ort.
Siehe auch
- Das ORM-Kochbuch — die Modelle, aus denen das Admin generiert wird (inkl. des geteilten Prüfpfads).
- HTML-Views — die generischen klassenbasierten Views, auf denen das Admin aufbaut.
- Auth-Backends · Sessions — das Admin hinter einem Login absichern.
- Sicherheitsleitfaden — Härtung, bevor du es öffentlich zugänglich machst.






