Let a connection's form offer options found on the remote service, several chosen, each with a mode #332

Closed
opened 2026-09-30 20:30:06 +00:00 by tiagoagueda · 0 comments
Owner

A connection's form is drawn from the plugin's FieldSpecs, with every choice fixed when config_fields() is called. The DAV plugin is asked to make sure it "can only read and/or write the desired / specified calendar(s), contact list(s), task(s), files(s), folder(s)", and only the server knows which address books, calendars and task lists exist and which the account may write (postulo/postulo-dav#13). A plugin can take only typed text; what it finds, it can only name in Test's sentence.

What the core has today

  • Field types are text, url, email, password, integer, boolean, choice and textarea; a choice must declare static, non-empty choices (src/postulo/plugins/base.py:515, 534-540). Nothing holds several values.
  • config_fields() is handed the owner at most (call_with_user, base.py:373-391; plugins/forms.py:154-157), never the stored configuration or the service. The one computed choice, the email notifier's verified addresses (#232), comes from Postulo's own records.
  • Test runs only on a saved connection, from the connections list, and records whether it worked and a sentence (plugins/views.py:361-411, plugins/models.py:271-279). It cannot write into the configuration.
  • validate(config) runs on submit, sees configuration and secrets together, and reports per field (plugins/forms.py:192-216).
  • A connection belongs to one person, who may hold several to one plugin (plugins/models.py:190). One connection per collection is today's only way to choose several.

Proposal

  1. Finding the options. An optional discover(config) on a connected plugin, looked up by name as validate and summary are. It returns, per field, the options found: a value (for DAV, an href), a label in words, an optional group ("Address books"), the modes the option may take, and an optional note ("Read only on the server"). Nothing in it is secret.
  2. Keeping them. The core stores the result, with when it was found, in the connection's non-secret config under a key the core owns. A form save already keeps keys that are not fields (plugins/forms.py:177-190).
  3. A field holding several options, each with a mode. A new field type whose FieldSpec declares its modes as (value, label) pairs (for DAV: read, write, read and write). It stores option value → mode, for chosen options only, drawn as a table: one row per option, grouped, each with a select offering Not used and the modes that option allows.
  4. Options that vanished stay in their row, marked "No longer on the server" with their last label and mode, until the person clears them. Never dropped silently; what a run does with one is the plugin's call.
  5. Before anything is found, a new connection's field says to save first and then look, as the consent section does (templates/connections/form.html:92-96).
  6. validate stays the last check and sees the choices and their modes.
  7. Bounded like Test. Discovery signs in with the person's password, so it shares POSTULO_CONNECTION_TEST_RATE (plugins/views.py:373-385). A failure is a sentence, and the stored options stay as they were.

The core draws the choice and does not enforce it; the plugin still holds its requests to it.

Possibly useful beyond DAV (untried): postulo-imap takes its folder, and postulo-paperless a tag and a custom field, by typed name; each could pick from what its server lists.

Decisions

  • When discovery runs. Recommended: its own button, Look on the server, on the saved connection's form, which then redraws it. Not on Test, which would then change what it only checks, nor whenever the form opens, which would sign in on every page view.
  • Where the options live. Recommended: in config, as above. A table of their own needs a migration and buys nothing.
  • One control per row. Recommended: a single select with Not used first, rather than a checkbox beside a mode select that means nothing while unchecked.
  • An option discovery did not find (a shared collection a server does not list; unverified). Recommended: not in this field; a plugin that needs it keeps a text field beside it.
  • Transports. Server settings → Email draws a transport's config_fields() with the same form_field_for (core/server_forms.py:560-562). Recommended: the new type for connections only, and a transport that declares it refused with a sentence.

Needed by

postulo/postulo-dav#13.

Done when

  • A plugin can declare the field and discover; a saved connection shows what was found, several chosen with a mode each and a vanished one in words, and validate sees it all.
  • The option record is on postulo.plugins.api beside FieldSpec (which gains modes), with its line in Writing a plugin (tests/test_wiki_surface.py), the published postulo.plugins.testing check and tests/test_plugin_surface.py. The wiki's Plugins that connect to another service describes the field, discover, what is stored, and that enforcement stays with the plugin.
  • The discovery address, a POST, is in the page-coverage walk's EXCUSED with a reason (tests/test_page_coverage.py) and in the isolation sweep (tests/security/test_isolation_sweep.py), answering 404 for somebody else's connection.
  • The table is a real table with a caption and header cells, every select labelled by its option's name, and works without JavaScript; the accessibility walk (tests/e2e/test_accessibility.py) visits a connection form showing found options. New strings are in en, fr-fr, pt-pt and pt-br (drafts).

From the list of DAV plugin improvements of 2026-09-30.

A connection's form is drawn from the plugin's `FieldSpec`s, with every choice fixed when `config_fields()` is called. The DAV plugin is asked to make sure it "can only read and/or write the desired / specified calendar(s), contact list(s), task(s), files(s), folder(s)", and only the server knows which address books, calendars and task lists exist and which the account may write (postulo/postulo-dav#13). A plugin can take only typed text; what it finds, it can only name in *Test*'s sentence. ## What the core has today - Field types are `text`, `url`, `email`, `password`, `integer`, `boolean`, `choice` and `textarea`; a `choice` must declare static, non-empty choices (`src/postulo/plugins/base.py:515, 534-540`). Nothing holds several values. - `config_fields()` is handed the owner at most (`call_with_user`, `base.py:373-391`; `plugins/forms.py:154-157`), never the stored configuration or the service. The one computed choice, the email notifier's verified addresses (#232), comes from Postulo's own records. - *Test* runs only on a saved connection, from the connections list, and records whether it worked and a sentence (`plugins/views.py:361-411`, `plugins/models.py:271-279`). It cannot write into the configuration. - `validate(config)` runs on submit, sees configuration and secrets together, and reports per field (`plugins/forms.py:192-216`). - A connection belongs to one person, who may hold several to one plugin (`plugins/models.py:190`). One connection per collection is today's only way to choose several. ## Proposal 1. **Finding the options.** An optional `discover(config)` on a connected plugin, looked up by name as `validate` and `summary` are. It returns, per field, the options found: a value (for DAV, an href), a label in words, an optional group ("Address books"), the modes the option may take, and an optional note ("Read only on the server"). Nothing in it is secret. 2. **Keeping them.** The core stores the result, with when it was found, in the connection's non-secret `config` under a key the core owns. A form save already keeps keys that are not fields (`plugins/forms.py:177-190`). 3. **A field holding several options, each with a mode.** A new field type whose `FieldSpec` declares its modes as `(value, label)` pairs (for DAV: read, write, read and write). It stores option value → mode, for chosen options only, drawn as a table: one row per option, grouped, each with a select offering *Not used* and the modes that option allows. 4. **Options that vanished** stay in their row, marked "No longer on the server" with their last label and mode, until the person clears them. Never dropped silently; what a run does with one is the plugin's call. 5. **Before anything is found**, a new connection's field says to save first and then look, as the consent section does (`templates/connections/form.html:92-96`). 6. **`validate` stays the last check** and sees the choices and their modes. 7. **Bounded like *Test*.** Discovery signs in with the person's password, so it shares `POSTULO_CONNECTION_TEST_RATE` (`plugins/views.py:373-385`). A failure is a sentence, and the stored options stay as they were. The core draws the choice and does not enforce it; the plugin still holds its requests to it. Possibly useful beyond DAV (untried): postulo-imap takes its folder, and postulo-paperless a tag and a custom field, by typed name; each could pick from what its server lists. ## Decisions - **When discovery runs.** Recommended: its own button, *Look on the server*, on the saved connection's form, which then redraws it. Not on *Test*, which would then change what it only checks, nor whenever the form opens, which would sign in on every page view. - **Where the options live.** Recommended: in `config`, as above. A table of their own needs a migration and buys nothing. - **One control per row.** Recommended: a single select with *Not used* first, rather than a checkbox beside a mode select that means nothing while unchecked. - **An option discovery did not find** (a shared collection a server does not list; unverified). Recommended: not in this field; a plugin that needs it keeps a text field beside it. - **Transports.** *Server settings → Email* draws a transport's `config_fields()` with the same `form_field_for` (`core/server_forms.py:560-562`). Recommended: the new type for connections only, and a transport that declares it refused with a sentence. ## Needed by postulo/postulo-dav#13. ## Done when - A plugin can declare the field and `discover`; a saved connection shows what was found, several chosen with a mode each and a vanished one in words, and `validate` sees it all. - The option record is on `postulo.plugins.api` beside `FieldSpec` (which gains `modes`), with its line in *Writing a plugin* (`tests/test_wiki_surface.py`), the published `postulo.plugins.testing` check and `tests/test_plugin_surface.py`. The wiki's *Plugins that connect to another service* describes the field, `discover`, what is stored, and that enforcement stays with the plugin. - The discovery address, a POST, is in the page-coverage walk's `EXCUSED` with a reason (`tests/test_page_coverage.py`) and in the isolation sweep (`tests/security/test_isolation_sweep.py`), answering 404 for somebody else's connection. - The table is a real table with a caption and header cells, every select labelled by its option's name, and works without JavaScript; the accessibility walk (`tests/e2e/test_accessibility.py`) visits a connection form showing found options. New strings are in en, fr-fr, pt-pt and pt-br (drafts). From the list of DAV plugin improvements of 2026-09-30.
tiagoagueda added this to the 0.5.0 milestone 2026-10-25 17:04:56 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo#332
No description provided.