ModelAdmin and save hooks
GisilaStudio.register builds a ModelAdmin from its arguments. You configure models through
register; ModelAdmin and buildModelAdmin are
exported so you can inspect or test the resolved configuration. GisilaStudio has no method
that accepts a prebuilt ModelAdmin.
StudioSaveHook
Section titled “StudioSaveHook”Transforms a submitted value before the studio writes it.
typedef StudioSaveHook = FutureOr<Object?> Function(Object? value);When to use: to hash a password, normalise an email, trim or validate a value. Hooks are the only extension point in the save path. For changes that span several columns or tables, use your own controller instead.
- Receives the parsed value: a
Stringfor text columns,int/double/DateTime/boolfor those types,nullfor a blank nullable field. - Returns the value to store. May be async.
- Throw a
FormatExceptionto reject the input; its message is shown on the field and the form is re-rendered with status422. - Runs on add and edit, only for columns that are being written. A password column left blank on
edit keeps its stored value and skips the hook. Columns in
readonlyFieldsorexcludeFieldsare never written, so their hooks never run. - Keys are column names; camelCase keys are resolved to snake_case columns like the other options.
Example
import 'package:gisila_studio/gisila_studio.dart';
studio.register<User>( UserTable.metadata, onSave: { 'password': (v) => hashPassword(v as String), 'email': (v) { final email = (v as String).trim().toLowerCase(); if (!email.contains('@')) throw const FormatException('Enter a valid email.'); return email; }, },);See also: Save hooks and custom actions.
ModelAdmin
Section titled “ModelAdmin”Configuration controlling how one model appears and behaves. All column lists are normalised to database column names when constructed.
class ModelAdmin<T> { ModelAdmin({ required this.meta, required this.displayName, required this.displayNamePlural, List<String>? listDisplay, List<String> searchFields = const [], List<String> readonlyFields = const [], List<String> excludeFields = const [], List<String>? passwordFields, List<String> autoNowOnUpdateFields = const [], Map<String, StudioSaveHook> onSave = const {}, this.pageSize = 25, List<String> ordering = const [], this.group, });}When to use: read it in tests to check how your register options resolve (for example
that 'firstName' became 'first_name'). Prefer buildModelAdmin, which fills in the display
names.
| Member | Type | Description |
|---|---|---|
meta |
TableMeta<T> |
The ORM metadata. |
displayName |
String |
Singular label. |
displayNamePlural |
String |
Plural label. |
listDisplay |
List<String>? |
Configured list columns, or null for the default. |
searchFields |
List<String> |
Search columns. |
readonlyFields |
List<String> |
Read-only form columns. |
excludeFields |
List<String> |
Columns left off forms. |
passwordFields |
List<String> |
Secret columns (default: names containing password, passwd or secret). |
autoNowOnUpdateFields |
List<String> |
Columns stamped with the UTC time on save. |
onSave |
Map<String, StudioSaveHook> |
Hooks keyed by column. |
pageSize |
int |
Rows per page. Default 25. |
ordering |
List<String> |
Order terms; - prefix kept for descending. |
group |
String? |
Sidebar group; null shows under “General”. |
effectiveListDisplay |
List<String> |
listDisplay, or the first 6 columns not in passwordFields. |
orderBySql |
String |
SQL ORDER BY terms, e.g. "date_joined" DESC, "email" ASC; the primary key ascending when ordering is empty. |
isEditable(column) |
bool |
Not in readonlyFields or excludeFields. |
isOnForm(column) |
bool |
Not in excludeFields. |
Sidebar groups are ordered with a few known labels first (Users & Auth, Catalog, Business,
Products, Commerce, Social, Moderation, Platform, General), then any others in
registration order.
Example
import 'package:test/test.dart';
test('camelCase names resolve to columns', () { final admin = buildModelAdmin<User>( UserTable.metadata, listDisplay: ['firstName', 'email'], ordering: ['-dateJoined'], ); expect(admin.listDisplay, ['first_name', 'email']); expect(admin.orderBySql, '"date_joined" DESC'); expect(admin.displayName, 'User');});buildModelAdmin
Section titled “buildModelAdmin”Builds a ModelAdmin with default display names. This is what register calls.
ModelAdmin<T> buildModelAdmin<T>( TableMeta<T> meta, { String? displayName, String? displayNamePlural, List<String>? listDisplay, List<String> searchFields = const [], List<String> readonlyFields = const [], List<String> excludeFields = const [], List<String>? passwordFields, List<String> autoNowOnUpdateFields = const [], Map<String, StudioSaveHook> onSave = const {}, int pageSize = 25, List<String> ordering = const [], String? group,});When to use: in tests, to get the same resolved configuration register would build.
displayName defaults to the table name split on _ and title-cased; displayNamePlural to
displayName + 's'. All other parameters are as in
register.