How the studio works
What it is
Section titled “What it is”A description of what happens between a browser request and the SQL the studio runs. Useful
when a field renders unexpectedly, a save fails, or you want to predict the effect of a
register option.
When to use it
Section titled “When to use it”- Debugging a form field type or a validation message.
- Understanding which columns are written on add and edit.
- Checking how expensive list pages are on large tables.
When not to use it
Section titled “When not to use it”For configuration options, go straight to the GisilaStudio reference.
How it works
Section titled “How it works”Routes
Section titled “Routes”handler(prefix: p) returns a Shelf handler with these routes, relative to the mount point.
<table> is the SQL table name (TableMeta.tableName), <pk> the primary key value.
| Method and path | Page or action |
|---|---|
GET / |
Dashboard: registered models grouped by group, with row counts |
GET /<table>/ |
List: ?q= search, ?page= pagination |
GET /<table>/add/ |
Add form |
POST /<table>/add/ |
Insert |
GET /<table>/<pk>/change/ |
Edit form |
POST /<table>/<pk>/change/ |
Update |
GET /<table>/<pk>/delete/ |
Delete confirmation |
POST /<table>/<pk>/delete/ |
Delete |
POST /<table>/action/ |
Bulk action (action=delete with ids) |
GET /login/, POST /login/ |
Sign-in (only with auth) |
POST /logout/ |
Sign-out (only with auth) |
A table that is not registered answers 404. prefix is used to build every link and redirect,
so it must equal the mount path.
Column metadata
Section titled “Column metadata”The studio combines two sources:
TableMetafrom gisila_orm gives the table name, the ordered column names and the primary key. Only columns inTableMeta.columnNamesappear on forms.- Postgres
information_schema.columns(schemapublic) gives each column’sdata_type, nullability and default. This is queried once per table and cached for the life of the process, so restart the server after a migration that changes column types.
Input types follow the Postgres type:
| Postgres type | Input |
|---|---|
integer, bigint, smallint, serial, bigserial |
number |
real, double precision, numeric, decimal |
number with decimals |
boolean |
checkbox |
date |
date |
time with[out] time zone |
time |
timestamp with[out] time zone |
datetime-local |
text, json, jsonb |
textarea |
| anything else | text |
A column in passwordFields is always a password input. A column in readonlyFields is shown
as text. A field is marked required when the column is NOT NULL, has no default and is not
read-only (and, on edit, is not a password field).
Saving
Section titled “Saving”On add and edit the studio walks TableMeta.columnNames and, for each column that is editable
(not in readonlyFields or excludeFields) and is not the primary key, parses the submitted
value:
- boolean: checked (
on,trueor1) istrue, anything else (including unchecked) isfalse. - blank value:
NULLif the column is nullable; left out of the write (so the database default applies, or on edit the stored value stays) if it has a default;''for text columns; otherwise the error “This field is required.” - numbers and dates: parsed; a value that does not parse is a field error (“Enter a whole number.”, “Enter a number.”, “Enter a valid date and time.”).
- edit, password field, blank: skipped, so the stored value stays.
Then the onSave hooks run for columns that have a value, autoNowOnUpdateFields are set to the
current UTC time, and the studio runs INSERT ... RETURNING * or UPDATE ... WHERE pk = $n.
Field errors re-render the form with status 422; a database error re-renders it with status
500 and the first line of the error.
After saving, the buttons choose the redirect: back to the list, to the edit page (“save and continue”), or to a new add form (“save and add another”).
- Columns:
listDisplay, or the first six columns not inpasswordFields. - Order:
ordering(prefix-for descending), or the primary key ascending. - Search: with
?q=, everysearchFieldscolumn that exists in the table is matched with"col"::text ILIKE '%q%', combined withOR. - Pagination:
pageSizerows per page,LIMIT/OFFSET.pagebelow 1 is treated as 1.
Each list request runs a COUNT(*) with the search filter plus the page query.
Row counts
Section titled “Row counts”The sidebar and dashboard show a count per registered table on every page. A table that Postgres
estimates (pg_class.reltuples) at 100,000 rows or more shows the estimate; smaller tables get
an exact COUNT(*).
Deletes
Section titled “Deletes”Single deletes go through the confirmation page. Bulk delete runs one
DELETE ... WHERE pk IN (...) for the selected rows. Primary key values that look like integers
are sent as integers, others as strings.
Example
Section titled “Example”How the options map to the pages for one model:
studio.register<User>( UserTable.metadata, listDisplay: ['id', 'email', 'isActive', 'dateJoined'], // list columns searchFields: ['email', 'firstName'], // ?q= targets readonlyFields: ['id', 'dateJoined'], // shown, not editable excludeFields: ['legacyFlags'], // not on forms autoNowOnUpdateFields: ['updatedAt'], // stamped on save ordering: ['-dateJoined'], // list order pageSize: 50,);Related
Section titled “Related”- Customize lists and forms
- ModelAdmin reference
- gisila_orm for
TableMetaandDatabase