Draw a page for each sync connection to pair, unpair or create records from its remote items, and show each pairing on its record #327

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

The DAV plugin improvements ask for pairing by hand. A card "can be 'paired' and data synced … to the user, to a person, to a department, or to a company". A CardDAV contact can be imported "as a new company (or update existing one)". Events and tasks are paired with interviews and other Postulo records. None of this has anywhere to happen today:

  • A plugin cannot add URLs, views, tables or scripts: the URLconf (src/postulo/config/urls.py) and INSTALLED_APPS are fixed, and the content security policy allows only Postulo's scripts. #228 settled it: "a plugin with pages or tables of its own has to be built into the image" (src/postulo/core/server_views.py:1398-1400).
  • SyncLink appears only in the Django admin, which is mounted only when POSTULO_ADMIN_URL is set. No record page shows a pairing.
  • suggest() files a guess about an application: a timeline entry or a status change. It cannot say "pair this card with that company".

Proposal

The core draws the page and the plugin supplies the data, which keeps to #228.

  1. The plugin's half. An optional SyncPlugin method lists the remote items the plugin can pair, filtered by a search and a record kind, or returns one item by its key. An item is a plain value on postulo.plugins.api, holding:

    • its collection (address and label), UID, address, label and a second line;
    • the record kinds it may pair with, from a vocabulary the core defines: to begin with contact, company, department, you, interview, reminder and application, and whatever postulo/postulo-dav#11 and postulo/postulo-dav#12 settle;
    • its slot (#326).

    A plugin without it gets no page.

  2. A page for each sync connection, under Settings → Connections. It opens on the paired items and the ones gone, listed by record from SyncLink without asking the server. Unpaired items come from the plugin, on a search or on Show unpaired; one already paired says with which record. For each item:

    • Pair with an existing record of an offered kind, found by a search of the person's own. This writes the link with no ETag; the next run (or Sync now, offered there) makes the first comparison.
    • Create a record from it, new or into an existing one, paired or not. The unpaired case is the one-off import of postulo/postulo-dav#9.
    • Unpair. Both sides are kept, and the plugin's next run neither pairs them again nor copies either side (#326).
    • Pair again a record whose twin is gone, or let the plugin make it a new twin.
  3. A line on each record's page: "Paired through ‹connection›, last synced ‹date›", or "Removed on the other side", with Unpair and Pair with an item…. It shows only what SyncLink holds, so a record page never waits on a server. It goes on company and application pages, on the edit pages of contacts, interviews and reminders (they have no other), on the company's page for a department, and on Your details for the person.

  4. Rules.

    • Only the owner sees or uses any of it.
    • The core takes an item's address from the plugin, never from the posted form: a forged address would be where the plugin sends the password on its next run. postulo/postulo-dav#13 guards the plugin's side.
    • Labels from the server are shown as escaped text, never as links.
    • Asking the plugin is rate-limited per account, as Test is (POSTULO_CONNECTION_TEST_RATE), with a rate of its own.
    • What the plugin fills in names it as the actor. Pairing and unpairing are the person's own.
  5. Testing. A helper in postulo.plugins.testing, beside listing_history, pairs, unpairs and creates as the page does, for a plugin's own tests.

Decisions

  • Live or stored.

    • (a) Ask the plugin when the page needs an item: nothing stale, no new table, no personal address book copied, but a slow server makes a slow page.
    • (b) Store what the last run saw. A SyncLink needs a record, so this is a new table of names and addresses from a personal address book.

    Recommended: (a), with a timeout and a search rather than a full list.

  • Who fills a record created from an item.

    • (a) A second optional method: the plugin writes the record through the surface's services (#328, #331), then the core binds the two.
    • (b) The core makes a bare record named after the label, and the next run fills it in.

    Recommended: (a). Only the plugin can read a vCard, and (b) cannot import once.

  • A queue as well, like Suggestions? Recommended: no; the plugin's adoption rules decide what arrives unasked.

Needed by

postulo/postulo-dav#8, postulo/postulo-dav#9, postulo/postulo-dav#11 and postulo/postulo-dav#12, and pairing and unpairing by hand in postulo/postulo-dav#7. It depends on #326. Record kinds beyond contact, interview and application wait for #328 and #331.

Done when

  • A sync plugin that has the method gets the page, and its records the line. Pair, unpair, pair again and create are each seen by its next run.
  • Every new address answers 404 for another account's connection or record (tests/security/test_isolation_sweep.py); a posted address is ignored.
  • The page is in the browser walk (tests/test_page_coverage.py). Its list is a table with a caption, and results arrive in a live region.
  • New names on postulo.plugins.api are in Writing a plugin (tests/test_wiki_surface.py) and asserted in tests/test_plugin_surface.py, and the helper in postulo.plugins.testing has a test of its own.
  • The words are in the working catalogues (en, fr-fr, pt-pt, pt-br as drafts).

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

The DAV plugin improvements ask for pairing by hand. A card "can be 'paired' and data synced … to the user, to a person, to a department, or to a company". A CardDAV contact can be imported "as a new company (or update existing one)". Events and tasks are paired with interviews and other Postulo records. None of this has anywhere to happen today: - A plugin cannot add URLs, views, tables or scripts: the URLconf (`src/postulo/config/urls.py`) and `INSTALLED_APPS` are fixed, and the content security policy allows only Postulo's scripts. #228 settled it: "a plugin with pages or tables of its own has to be built into the image" (`src/postulo/core/server_views.py:1398-1400`). - `SyncLink` appears only in the Django admin, which is mounted only when `POSTULO_ADMIN_URL` is set. No record page shows a pairing. - `suggest()` files a guess about an application: a timeline entry or a status change. It cannot say "pair this card with that company". ## Proposal The core draws the page and the plugin supplies the data, which keeps to #228. 1. **The plugin's half.** An optional `SyncPlugin` method lists the remote items the plugin can pair, filtered by a search and a record kind, or returns one item by its key. An item is a plain value on `postulo.plugins.api`, holding: - its collection (address and label), `UID`, address, label and a second line; - the record kinds it may pair with, from a vocabulary the core defines: to begin with contact, company, department, you, interview, reminder and application, and whatever postulo/postulo-dav#11 and postulo/postulo-dav#12 settle; - its slot (#326). A plugin without it gets no page. 2. **A page for each sync connection**, under *Settings → Connections*. It opens on the paired items and the ones gone, listed by record from `SyncLink` without asking the server. Unpaired items come from the plugin, on a search or on *Show unpaired*; one already paired says with which record. For each item: - **Pair** with an existing record of an offered kind, found by a search of the person's own. This writes the link with no ETag; the next run (or *Sync now*, offered there) makes the first comparison. - **Create a record from it**, new or into an existing one, paired or not. The unpaired case is the one-off import of postulo/postulo-dav#9. - **Unpair.** Both sides are kept, and the plugin's next run neither pairs them again nor copies either side (#326). - **Pair again** a record whose twin is gone, or let the plugin make it a new twin. 3. **A line on each record's page**: "Paired through ‹connection›, last synced ‹date›", or "Removed on the other side", with *Unpair* and *Pair with an item…*. It shows only what `SyncLink` holds, so a record page never waits on a server. It goes on company and application pages, on the edit pages of contacts, interviews and reminders (they have no other), on the company's page for a department, and on *Your details* for the person. 4. **Rules.** - Only the owner sees or uses any of it. - The core takes an item's address from the plugin, never from the posted form: a forged address would be where the plugin sends the password on its next run. postulo/postulo-dav#13 guards the plugin's side. - Labels from the server are shown as escaped text, never as links. - Asking the plugin is rate-limited per account, as *Test* is (`POSTULO_CONNECTION_TEST_RATE`), with a rate of its own. - What the plugin fills in names it as the actor. Pairing and unpairing are the person's own. 5. **Testing.** A helper in `postulo.plugins.testing`, beside `listing_history`, pairs, unpairs and creates as the page does, for a plugin's own tests. ## Decisions - **Live or stored.** - (a) Ask the plugin when the page needs an item: nothing stale, no new table, no personal address book copied, but a slow server makes a slow page. - (b) Store what the last run saw. A `SyncLink` needs a record, so this is a new table of names and addresses from a personal address book. Recommended: (a), with a timeout and a search rather than a full list. - **Who fills a record created from an item.** - (a) A second optional method: the plugin writes the record through the surface's services (#328, #331), then the core binds the two. - (b) The core makes a bare record named after the label, and the next run fills it in. Recommended: (a). Only the plugin can read a vCard, and (b) cannot import once. - **A queue as well**, like *Suggestions*? Recommended: no; the plugin's adoption rules decide what arrives unasked. ## Needed by postulo/postulo-dav#8, postulo/postulo-dav#9, postulo/postulo-dav#11 and postulo/postulo-dav#12, and pairing and unpairing by hand in postulo/postulo-dav#7. It depends on #326. Record kinds beyond contact, interview and application wait for #328 and #331. ## Done when - A sync plugin that has the method gets the page, and its records the line. Pair, unpair, pair again and create are each seen by its next run. - Every new address answers 404 for another account's connection or record (`tests/security/test_isolation_sweep.py`); a posted address is ignored. - The page is in the browser walk (`tests/test_page_coverage.py`). Its list is a table with a caption, and results arrive in a live region. - New names on `postulo.plugins.api` are in *Writing a plugin* (`tests/test_wiki_surface.py`) and asserted in `tests/test_plugin_surface.py`, and the helper in `postulo.plugins.testing` has a test of its own. - The words are in the working catalogues (en, fr-fr, pt-pt, pt-br as 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#327
No description provided.