Put schedule_interview and a read-only view of the calendar's other dated things on the plugin surface #330

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

For interviews, postulo.plugins.api offers Interview and InterviewOutcome, reschedule_interview and settle_interview (each with an actor), and the calendar text event_lines and calendar_status. That was enough for postulo-dav to push interviews and pull edits to them. It is not enough for what the plugin is now asked to do, pair an event made elsewhere with an interview or another dated thing on Postulo (postulo/postulo-dav#11), in two places:

  • Nothing makes an interview. schedule_interview (src/postulo/applications/services.py:378) is off the surface, and so is InterviewKind, the vocabulary it takes. It writes the interview_scheduled entry, adds a reminder a day ahead, and records an interview that is already over as held (:410-411). A plugin cannot do the same: saving an Interview skips all three, and Reminder is off the surface too. Accepting a suggestion never creates an interview (applications/suggestions.py:69), so suggest is no way round. The checks its callers make first (a known kind, the end after the start, contacts that are the owner's and at the application's company: api/routers/interviews.py:97-126, applications/forms.py:571-583) are not in the service.
  • The other dated things are only partly in reach. Besides interviews, the Calendar page draws reminders, application deadlines, listing closing dates and offer answer-by dates, all read through agenda.events_between (applications/agenda.py:197); the web feed writes the last three as whole days with DayEntry and day_lines (ical.py:202-245). None of these is on the surface, nor are Offer and JobPosting. A plugin can reach an application's deadline, and its listing's closes_at, through Application; it cannot list the listings nobody has applied to, and it would have to restate which dates are over and what they are called.

Proposal

  1. schedule_interview on the surface, with an actor, and InterviewKind beside it. The checks the form and the API make move into the call, so a plugin cannot skip them: a kind that is an InterviewKind, an end after the start, and every contact the owner's and at the application's company, refused otherwise as record_listing_event refuses another person's contact (jobs/history.py:163-164).
  2. A read-only view of the calendar's other dated things: one call over events_between, for a person and a range of days, answering plain values: kind, title, detail, the moment or the day, whether it is over, the absolute link, and for a whole day its DayEntry, with day_lines on the surface to write it as the feed does. Reminder records and their services are #331.

Each new name gets its line in both tables of the wiki's Writing a plugin (tests/test_wiki_surface.py) and in tests/test_plugin_surface.py (test_the_surface_holds_what_a_sync_needs), and the published postulo.plugins.testing check passes a plugin using them. No page is added. No user-facing string is expected; any that appears goes in the working catalogues (en, fr-fr, pt-pt, pt-br as drafts).

Decisions

  • Whether event_lines needs a uid=. It always writes UID:{interview.uid} (applications/ical.py:172), at most 64 characters (applications/models.py:734), and CalDAV forbids changing an existing event's UID (RFC 4791 §5.3.2.1). But postulo/postulo-dav#11 recommends patching an event made elsewhere, never rebuilding it, and a plugin can take the lines it owns from event_lines and leave the UID out. Recommend leaving event_lines as it is. Alternative: a keyword-only uid=, if postulo/postulo-dav#11 settles on rebuilding paired events whole. Either way the remote UID stays on the SyncLink (200 characters), not in Interview.uid, which is unique per person while a UID is unique only per calendar (RFC 4791 §4.1).
  • Values or records. Recommend plain values over putting Offer and JobPosting on the surface: events_between is "the one place that decides what a deadline is" (applications/views.py:1059), so the page, the feed and a plugin cannot disagree.
  • Whose time zone. events_between bounds days in the active zone, and a scheduled sync activates none (plugins/syncing.py). Recommend the call reads in the person's zone, rather than leaving it to the caller.
  • Whole-day UIDs without a request. DayEntry.uid is a UUID over the entry's address, or its summary when it has none (ical.py:214-217). With POSTULO_PUBLIC_URL unset a scheduled job has only a path, and a UUID over a path is the collision between instances the module warns about. Recommend: with it unset, the call answers no whole days and says why. Alternative: day_lines takes a UID the plugin mints and keeps on the link.
  • An absolute link. Not proposed: a plugin can read POSTULO_PUBLIC_URL, documented in the wiki's Configuration, from Django's settings and join it with a record's get_absolute_url(). Alternative: absolute_url (notifications/base.py:154) on the surface, so the rule lives in one place.

Needed by

  • postulo/postulo-dav#11: making an interview from an event, and pushing whole days one way (its recommendation for "other event on Postulo").

Done when

  • A plugin makes an interview through the surface, with the same timeline entry, reminder and refusals as the form and the API.
  • A plugin run from the scheduler reads a person's deadlines, closing dates and answer dates for a range of days in their time zone, as the Calendar page shows them.
  • The wiki and both surface tests list every new name.

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

For interviews, `postulo.plugins.api` offers `Interview` and `InterviewOutcome`, `reschedule_interview` and `settle_interview` (each with an actor), and the calendar text `event_lines` and `calendar_status`. That was enough for postulo-dav to push interviews and pull edits to them. It is not enough for what the plugin is now asked to do, pair an event made elsewhere with an interview or another dated thing on Postulo (postulo/postulo-dav#11), in two places: - **Nothing makes an interview.** `schedule_interview` (`src/postulo/applications/services.py:378`) is off the surface, and so is `InterviewKind`, the vocabulary it takes. It writes the `interview_scheduled` entry, adds a reminder a day ahead, and records an interview that is already over as held (`:410-411`). A plugin cannot do the same: saving an `Interview` skips all three, and `Reminder` is off the surface too. Accepting a suggestion never creates an interview (`applications/suggestions.py:69`), so `suggest` is no way round. The checks its callers make first (a known kind, the end after the start, contacts that are the owner's and at the application's company: `api/routers/interviews.py:97-126`, `applications/forms.py:571-583`) are not in the service. - **The other dated things are only partly in reach.** Besides interviews, the Calendar page draws reminders, application deadlines, listing closing dates and offer answer-by dates, all read through `agenda.events_between` (`applications/agenda.py:197`); the web feed writes the last three as whole days with `DayEntry` and `day_lines` (`ical.py:202-245`). None of these is on the surface, nor are `Offer` and `JobPosting`. A plugin can reach an application's `deadline`, and its listing's `closes_at`, through `Application`; it cannot list the listings nobody has applied to, and it would have to restate which dates are over and what they are called. ## Proposal 1. **`schedule_interview` on the surface, with an actor, and `InterviewKind` beside it.** The checks the form and the API make move into the call, so a plugin cannot skip them: a kind that is an `InterviewKind`, an end after the start, and every contact the owner's and at the application's company, refused otherwise as `record_listing_event` refuses another person's contact (`jobs/history.py:163-164`). 2. **A read-only view of the calendar's other dated things**: one call over `events_between`, for a person and a range of days, answering plain values: kind, title, detail, the moment or the day, whether it is over, the absolute link, and for a whole day its `DayEntry`, with `day_lines` on the surface to write it as the feed does. Reminder records and their services are #331. Each new name gets its line in both tables of the wiki's *Writing a plugin* (`tests/test_wiki_surface.py`) and in `tests/test_plugin_surface.py` (`test_the_surface_holds_what_a_sync_needs`), and the published `postulo.plugins.testing` check passes a plugin using them. No page is added. No user-facing string is expected; any that appears goes in the working catalogues (en, fr-fr, pt-pt, pt-br as drafts). ## Decisions - **Whether `event_lines` needs a `uid=`.** It always writes `UID:{interview.uid}` (`applications/ical.py:172`), at most 64 characters (`applications/models.py:734`), and CalDAV forbids changing an existing event's UID (RFC 4791 §5.3.2.1). But postulo/postulo-dav#11 recommends patching an event made elsewhere, never rebuilding it, and a plugin can take the lines it owns from `event_lines` and leave the `UID` out. Recommend leaving `event_lines` as it is. Alternative: a keyword-only `uid=`, if postulo/postulo-dav#11 settles on rebuilding paired events whole. Either way the remote UID stays on the `SyncLink` (200 characters), not in `Interview.uid`, which is unique per person while a UID is unique only per calendar (RFC 4791 §4.1). - **Values or records.** Recommend plain values over putting `Offer` and `JobPosting` on the surface: `events_between` is "the one place that decides what a deadline is" (`applications/views.py:1059`), so the page, the feed and a plugin cannot disagree. - **Whose time zone.** `events_between` bounds days in the active zone, and a scheduled sync activates none (`plugins/syncing.py`). Recommend the call reads in the person's zone, rather than leaving it to the caller. - **Whole-day UIDs without a request.** `DayEntry.uid` is a UUID over the entry's address, or its summary when it has none (`ical.py:214-217`). With `POSTULO_PUBLIC_URL` unset a scheduled job has only a path, and a UUID over a path is the collision between instances the module warns about. Recommend: with it unset, the call answers no whole days and says why. Alternative: `day_lines` takes a UID the plugin mints and keeps on the link. - **An absolute link.** Not proposed: a plugin can read `POSTULO_PUBLIC_URL`, documented in the wiki's *Configuration*, from Django's settings and join it with a record's `get_absolute_url()`. Alternative: `absolute_url` (`notifications/base.py:154`) on the surface, so the rule lives in one place. ## Needed by - postulo/postulo-dav#11: making an interview from an event, and pushing whole days one way (its recommendation for "other event on Postulo"). ## Done when - A plugin makes an interview through the surface, with the same timeline entry, reminder and refusals as the form and the API. - A plugin run from the scheduler reads a person's deadlines, closing dates and answer dates for a range of days in their time zone, as the Calendar page shows them. - The wiki and both surface tests list every new name. 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#330
No description provided.