Repository navigation
v0.4.0
Added
-
JSON fields.
tango:"json"on astringfield declares it holds JSON text (model.FieldMeta.JSON).Store.CreateandStore.Updaterefuse text that is not valid JSON with a*db.InvalidJSONErrorthat matchesdb.ErrInvalidJSONand never contains the submitted text; the empty string is unset,nullis valid, and the stored text is exactly what was given. It is ordinaryTEXTon SQLite and PostgreSQL, so adding the tag to an existing field generates no migration and does not scan existing rows; reads are not checked.jsoncannot be combined withvarchar=n,text,pk,fk,uniqueorindex. See ADR 0059. -
JSON fields in the admin. A
tango:"json"field getsadmin.JSON()by default (Edit and Formatted tabs) with noOptions.Widgetsentry; an explicit entry still wins, and read-only and changelist rendering are unchanged. A save the store refuses for invalid JSON, including through a non-JSON widget such asadmin.Textarea(), re-renders with a 422 and a translatable field error (admin.error.invalid_json, "Not valid JSON.") beside the field, keeping the text as typed. See admin registration. -
Admin refinement.
admin.JSON()edits a string field as JSON (syntax checked on save, the submitted text stored exactly, a browser-side Formatted tab).admin.Markdown(...)edits Markdown in a monospace textarea with a character count and, when you passadmin.WithMarkdownPreview, a sandboxed Preview tab from your own renderer: tanGO ships no parser or sanitizer, and you decide how HTML is sanitized (ADR 0058).Options.Filtersadds boolean and foreign-key filters to the changelist (they combine with search and ordering; an over-large foreign-key filter still applies a primary key given in the URL).Options.Displaywithadmin.DisplayFuncandadmin.Displayrenders computed changelist cells, such as a file's size and an authorized download link, without admin knowing about storage;admin.ByteSizeandadmin.ShortHashhelp.admin.FieldErrorlets a widget report an error beside its own field. Forms now renderHelpTextand mark invalid fields witharia-invalidandaria-describedby; the changelist gets a labelled search box and filters, a caption and column scopes.Options.Labelsnow also names a field's changelist column when set. See admin registration and ADRs 0057 and 0058. -
Bounded strings.
tango:"varchar=n"declares astringfield of at mostncharacters (1 to 10,485,760), andtango:"text"states an unbounded one explicitly; a barestringstays unbounded text and generates no migration.model.FieldMeta.MaxLengthcarries the limit and a malformed tag failsRegisterwithmodel.ErrInvalidFieldTag.Store.CreateandStore.Updaterefuse a longer value before any SQL with a*db.ValueTooLongError(db.ErrValueTooLong), counting runes the way PostgreSQL counts characters; SQLite does not enforce the length and raw SQL is not checked.tango makemigrationswrites aVARCHAR(n)column (Column.Length, additive, with noCHECK), generates the widening changes (varchar(n)totextor a largervarchar) and the narrowing ones (texttovarchar(n)or a smallervarchar), and a narrowing checks the existing data in Go first, failing with the number of rows and one offending key instead of truncating anything. A migration of narrowings is reversible; a widening makes its migration irreversible. The admin rendersmaxlengthand shows a translatable per-field message (admin.error.too_long) for a refused save, andadmin.FieldContextgainsMaxLength. Built-in models are not retagged. See models and tags and changing a field's type. -
Accounts for a single-page app.
accounts.WithJSONmounts six bearer-only JSON endpoints under/accounts/api/(register, login, password reset request and confirm, verify, resend verification) for a separate client, sharing the HTML flow's rules, limiters and outbox. The host supplies the token side throughaccounts.JSONAuth(Issue,Authenticate) andAuthGrant, soaccountsimports no token package; JSON mode never reads or sets a cookie and has no logout.JSONConfigalso takes the emailed link templatesVerifyURLandResetURL({token}once; prefer a fragment), anOnRegisterhook that runs inside the account's transaction with aRegistration(aFieldErroror*db.ValueTooLongErroranswers422), andResolveIdentifierso login can take a username. Requests are strict (415,413,400 invalid_body), errors are{"error", "code", "fields"?}, and every response isno-store. Newdb.Store.InTxruns a callback in one transaction (commit, rollback on error, rollback and re-panic on panic; nested calls join). See the accounts guide, the newexamples/spa-accountsand ADRs 0050 and 0051. -
Accounts answers invalid JSON as a field error. In the JSON accounts API, a
*db.InvalidJSONErrorfrom a registration hook'stx.Create(alone or joined witherrors.Join) answers422 invalid_field, keyed by the Go field name with the messagemust be valid JSON, instead of a generic500; the document is never echoed. See the accounts guide. -
File uploads and storage. The new
storagepackage is an explicit object store for uploaded files, used from ordinary Views:Putstreams a file in, always sniffs its content type, enforces a required size cap and an optional allowed-types list, and returns a generated opaque key with the size and SHA-256;Stat,Open(a byte range) andDeletecomplete theStore. There is noFileField, no file model, no listing and no signed URLs: the host keeps the key and metadata in its own model and authorizes every access.storage.Uploadreads one multipart file with bounded memory, andstorage.Servedelivers an authorized object with ranges, conditional requests, attachment by default andnosniff. Backends:storage/local(atomic,os.Root-contained, for development and one host),storage/s3(Amazon S3, Cloudflare R2 and other S3-compatible providers, in its own Go module so no cloud SDK reaches yourgo.mod), andstoragetestwith an in-memoryStoreand the conformance suite everyStorepasses. See the uploads guide, the newexamples/uploadsand ADRs 0052 to 0054. Native Google Cloud Storage is a later release. -
Caching. The new
cachepackage is a small, explicit cache for recomputable reads: a bytes-onlycache.Store(Get,Setwith a TTL of up to 30 days,Delete) where a miss is not an error, one portable key rule,cache.Prefixfor namespaces and bulk invalidation by version, typed JSON helpers (GetJSON,SetJSON,ErrCorrupt), and the fail-openFetchJSONread-through that reports cache failures to anOnErrorhook and never fails the request because the cache is down.cache/localis a bounded in-process store (entry limit, optional byte budget, least-recently-used eviction);cache/redis(Redis and Valkey) andcache/memcacheare their own Go modules, so no client library reaches yourgo.mod.cachetest.Runis the conformance suite every backend passes. There is no response, fragment or ORM caching, no automatic invalidation, and nothing logs. See the caching guide, the newexamples/cacheand ADRs 0055 and 0056.
Changed
-
An admin changelist
?page=beyond the last page now shows the last page, so the rows and the pager always describe the same page (before, it showed an empty list under a pager that named the last page). -
The admin foreign key select now starts with a "— None —" choice (translation key
admin.fk.none), and an unset foreign key shows an em dash in the changelist instead of0. Saving "None" stores the same unset value (NULL) as before. -
POST /admin/logout/now requires the session's CSRF token when the request carries a session cookie, so a cross-site form cannot sign an admin out. A missing or wrong token gets 403 and leaves the session in place. A request without a session cookie still redirects to the login page. -
The admin sidebar footer no longer says "Signed in over Basic Auth" (admin has used session logins since 0.3). It now shows "Signed in as " with a Log out button that posts to
/admin/logout/with the session's CSRF token. The translation keyadmin.layout.signed_inis replaced byadmin.layout.signed_in_asandadmin.layout.logout. -
/accounts/login/now always spends one password comparison, even for an unknown or inactive account, so its timing no longer says whether an account exists. Nothing else about the HTML flow changes.