Put companies, departments and the person's own card on the plugin surface, with services to write companies, departments, contacts and their rows #328

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

postulo.plugins.api hands out the records Application, Contact, Interview, Suggestion and SyncLink; its writes are the timeline and interview calls, suggest, record_listing_event, get_or_create_company (by name or Wikidata id), and helpers for a holder's primary number or link only. The DAV improvements ask to pair a card "to the user, to a person, to a department, or to a company" (the user read here as the account holder, whose details are User and Profile; postulo/postulo-dav#8 settles that reading), to import one "as a new company (or update existing one)", and to export from Postulo. None of that can be written against the surface, and postulo-dav's tests/test_surface.py fails any import past it.

What is missing today

  • Company is not on the surface (src/postulo/plugins/api.py), and no service writes its website, careers_url, location, notes, kind or parent: the API sets attributes and saves (add_company, patch_company in src/postulo/api/routers/companies.py).
  • Department, a name under a company (src/postulo/jobs/models.py), is not on it; only the contact form and the archive import create one (src/postulo/jobs/forms.py, src/postulo/core/importer.py).
  • Contacts are written by saving the model. The DAV plugin calls Contact.objects.create and contact.save() (postulo-dav, src/postulo_dav/sync.py), against Writing a plugin's "Never write by saving a model"; the API's add_contact does the same.
  • Primaries only. save_phone_number and save_web_link edit the primary row and touch nothing else (src/postulo/core/phone_numbers.py, src/postulo/core/web_links.py). No surface call reaches the other rows, and only the profile page's formset saves a postal address (src/postulo/core/postal.py).
  • The person's own details are off the surface. contact_details(owner) in src/postulo/documents/rendering.py assembles them, but for a CV header: one number, no address.

Proposal

Read:

  • Company and Department, scoped with for_user(), and the vocabularies a card maps onto: CompanyKind and the kinds of telephone number and postal address.
  • A holder's numbers, addresses and links as plain values (kind, label, primary), following the person's switches: with Several telephone numbers off, the primary alone (numbers_for, links_for).
  • The person's card, read only, built beside contact_details: given and family name, the sign-in address, headline, location, picture (Profile.picture), and their numbers, addresses and links as above.

Write, each with a keyword actor, taking the owner from the record handed in where there is one, as record_listing_event does, and refusing another owner's record:

  • update_company: name, website, careers page, location, notes, kind, parent. A name another of the person's companies has is refused (unique_company_name_per_owner), never merged (#239).
  • get_or_create_department(company, name).
  • create_contact and update_contact, running Contact.clean, so a department of another company is refused.
  • save_phone_numbers, save_postal_addresses, save_web_links: make a holder's rows these, keeping a row that stays and settling one primary. A row the uniqueness rules refuse comes back with the reason instead of raising, and hearing it spends the #142 allowance as a form does. With a switch off, only the primary is written, as save_phone_number does today.

Identifiers stay off (set_identifiers included): RFC 6350 has no property for a registry number, a Wikidata id or an ORCID iD.

The names are proposals. Each needs its line in Writing a plugin (tests/test_wiki_surface.py), a test in tests/test_plugin_surface.py and a changelog line; the published check in postulo.plugins.testing compares imports with the surface module itself and needs no change.

Decisions

  • The person's own details: read only, or written back. Recommended: read only. Written back, a card could change the sign-in address (User.email, unique), a verified number or the recovery number (#142, #144). The alternative is the importer's rule, fill blanks and never overwrite (Record.person in src/postulo/resume/importing.py), still leaving those three alone, and can come later.
  • Which email the card carries. Recommended: the sign-in address. The alternative adds allauth's other rows (postulo.plugins.email_addresses), some kept as a way back in rather than to give out.
  • What actor records. actor is a column only on ApplicationEvent and ListingEvent; companies, departments and contacts have no history. Recommended: take it now, so the signature lasts, and record nothing with it until these records have a history of their own. The alternative is a line in the record's notes.
  • Structure while employer structure is off. Departments and parents are then hidden and kept (src/postulo/jobs/structure.py). Recommended: reads work either way; creating a department, putting a contact in one or setting a parent is refused, so a sync cannot build what the person is not shown. The alternative lets a phone fill in a structure nobody sees until the feature is back.

Needed by

postulo/postulo-dav#8, postulo/postulo-dav#9 and postulo/postulo-dav#10. A company holds numbers and addresses only once #329 lands; until then the row services take a contact.

Done when

  • A plugin importing only postulo.plugins.api can read a company, its departments and the person's card; update a company; create a department and a contact; and write several numbers, addresses and links.
  • The DAV plugin's Contact.objects.create and contact.save() can go; the API's add_contact uses the same service.
  • With a switch off, a read returns the primaries and a write leaves the hidden rows as they were; another owner's record is refused; with the person's details read only, nothing on the surface writes User or Profile. A test each.

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

`postulo.plugins.api` hands out the records `Application`, `Contact`, `Interview`, `Suggestion` and `SyncLink`; its writes are the timeline and interview calls, `suggest`, `record_listing_event`, `get_or_create_company` (by name or Wikidata id), and helpers for a holder's **primary** number or link only. The DAV improvements ask to pair a card "to the user, to a person, to a department, or to a company" (the user read here as the account holder, whose details are `User` and `Profile`; postulo/postulo-dav#8 settles that reading), to import one "as a new company (or update existing one)", and to export from Postulo. None of that can be written against the surface, and postulo-dav's `tests/test_surface.py` fails any import past it. ## What is missing today - **`Company`** is not on the surface (`src/postulo/plugins/api.py`), and no service writes its `website`, `careers_url`, `location`, `notes`, `kind` or `parent`: the API sets attributes and saves (`add_company`, `patch_company` in `src/postulo/api/routers/companies.py`). - **`Department`**, a name under a company (`src/postulo/jobs/models.py`), is not on it; only the contact form and the archive import create one (`src/postulo/jobs/forms.py`, `src/postulo/core/importer.py`). - **Contacts are written by saving the model.** The DAV plugin calls `Contact.objects.create` and `contact.save()` (postulo-dav, `src/postulo_dav/sync.py`), against *Writing a plugin*'s "Never write by saving a model"; the API's `add_contact` does the same. - **Primaries only.** `save_phone_number` and `save_web_link` edit the primary row and touch nothing else (`src/postulo/core/phone_numbers.py`, `src/postulo/core/web_links.py`). No surface call reaches the other rows, and only the profile page's formset saves a postal address (`src/postulo/core/postal.py`). - **The person's own details** are off the surface. `contact_details(owner)` in `src/postulo/documents/rendering.py` assembles them, but for a CV header: one number, no address. ## Proposal Read: - `Company` and `Department`, scoped with `for_user()`, and the vocabularies a card maps onto: `CompanyKind` and the kinds of telephone number and postal address. - A holder's numbers, addresses and links as plain values (kind, label, primary), following the person's switches: with *Several telephone numbers* off, the primary alone (`numbers_for`, `links_for`). - **The person's card**, read only, built beside `contact_details`: given and family name, the sign-in address, headline, location, picture (`Profile.picture`), and their numbers, addresses and links as above. Write, each with a keyword `actor`, taking the owner from the record handed in where there is one, as `record_listing_event` does, and refusing another owner's record: - `update_company`: name, website, careers page, location, notes, kind, parent. A name another of the person's companies has is refused (`unique_company_name_per_owner`), never merged (#239). - `get_or_create_department(company, name)`. - `create_contact` and `update_contact`, running `Contact.clean`, so a department of another company is refused. - `save_phone_numbers`, `save_postal_addresses`, `save_web_links`: make a holder's rows these, keeping a row that stays and settling one primary. A row the uniqueness rules refuse comes back with the reason instead of raising, and hearing it spends the #142 allowance as a form does. With a switch off, only the primary is written, as `save_phone_number` does today. Identifiers stay off (`set_identifiers` included): RFC 6350 has no property for a registry number, a Wikidata id or an ORCID iD. The names are proposals. Each needs its line in *Writing a plugin* (`tests/test_wiki_surface.py`), a test in `tests/test_plugin_surface.py` and a changelog line; the published check in `postulo.plugins.testing` compares imports with the surface module itself and needs no change. ## Decisions - **The person's own details: read only, or written back.** Recommended: read only. Written back, a card could change the sign-in address (`User.email`, unique), a verified number or the recovery number (#142, #144). The alternative is the importer's rule, fill blanks and never overwrite (`Record.person` in `src/postulo/resume/importing.py`), still leaving those three alone, and can come later. - **Which email the card carries.** Recommended: the sign-in address. The alternative adds allauth's other rows (`postulo.plugins.email_addresses`), some kept as a way back in rather than to give out. - **What `actor` records.** `actor` is a column only on `ApplicationEvent` and `ListingEvent`; companies, departments and contacts have no history. Recommended: take it now, so the signature lasts, and record nothing with it until these records have a history of their own. The alternative is a line in the record's notes. - **Structure while *employer structure* is off.** Departments and parents are then hidden and kept (`src/postulo/jobs/structure.py`). Recommended: reads work either way; creating a department, putting a contact in one or setting a parent is refused, so a sync cannot build what the person is not shown. The alternative lets a phone fill in a structure nobody sees until the feature is back. ## Needed by postulo/postulo-dav#8, postulo/postulo-dav#9 and postulo/postulo-dav#10. A company holds numbers and addresses only once #329 lands; until then the row services take a contact. ## Done when - A plugin importing only `postulo.plugins.api` can read a company, its departments and the person's card; update a company; create a department and a contact; and write several numbers, addresses and links. - The DAV plugin's `Contact.objects.create` and `contact.save()` can go; the API's `add_contact` uses the same service. - With a switch off, a read returns the primaries and a write leaves the hidden rows as they were; another owner's record is refused; with the person's details read only, nothing on the surface writes `User` or `Profile`. A test each. From the list of DAV plugin improvements of 2026-09-30.
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#328
No description provided.