A document is a kind, and a kind is a plugin #133
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Depends on
Reference
Postulo/postulo#133
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Observation
What exists
More than "one kind", and less than five. Authored:
CV, which selects from the careerrecord through
CVItem; andCoverLetter, which already carries four shapes of its own —cover, motivation, speculative, follow-up — told apart by shape rather than by name, with
a note about why lettre de motivation makes the naming a translation hazard.
Held rather than authored:
UploadedDocument, whoseDocumentKindalready listsPORTFOLIO,CERTIFICATEandREFERENCE— so three of the kinds asked for exist asfiles somebody had, and none of them as things Postulo makes.
Frozen:
RenderedDocument, the PDF as it went out, with its source text beside it so"what did I claim?" is answerable without opening anything. Copied:
DocumentCopy, one rowper document per store connection, which is the half the future Paperless plugin plugs into
and it is already generic over stores —
store,label,external_id,external_url,retries and a status — and not generic over documents.
What this asks for
Five kinds, each an internal plugin, all playing nicely with themes, stores, and more than
one language at a time.
Worth being careful about
Three of the five are not the same animal, and the differences are structural.
Portfolios are CV-shaped: a selection from the career record with its own template. They
would use
CVItem's machinery almost unchanged, and they are where "of different kinds"bites — a developer's portfolio, a designer's and a researcher's differ in what they select
and how it is laid out, which is either three kinds or one kind with three themes.
Reports (#56) are the first kind whose content is computed, not written. Nothing is
selected and nothing is typed: the document is a query over the record at a moment. That
fits
RenderedDocumentwell — a report handed to an employment office should be frozenexactly as handed over — but it means "edit this document" has no meaning for the kind, and
the interface has to stop offering it.
Emails cross a boundary the application currently keeps clean. Postulo sends mail through
a
transport, and #104 locks the transport when it is the last way back into an account. Anemail that is a document is something composed, rendered, frozen and copied to a store; an
email that is a message is something a transport carries. Which of those this kind is
decides whether it belongs in
documentsat all, and the answer is not obvious: the usefulthing about keeping the email you sent a recruiter is exactly the thing
RenderedDocumentalready does for a letter.
A kind as a plugin needs a contract, and it is a wide one. A source is handed a URL and
gives back data. A document kind has to declare a model or say it has none, a template per
theme, what a new one starts as (
LETTER_STARTERSis the precedent), how it renders toHTML, how it snapshots to a PDF, and what it is called in the list. That is a much larger
surface than any existing kind, which is why #126 and #129 come first: without a declared
surface and a self-containment rule, five document kinds inside
documents/would be fivemore
phone-numbers.Stores already work, and should keep working without knowing about kinds.
archiving.pyschedules a copy per document per connection and asks
isinstance(document, RenderedDocument). Once the link is polymorphic, a Paperless plugin receives a file, atitle and a kind, and never learns that portfolios exist — which is the right shape and
worth protecting deliberately rather than by accident.
DocumentKindis doing two jobs already. It labels an uploaded file and a renderedone, and
CoverLettermaps its ownLetterKindonto it. Five plugin kinds and thisenumeration will disagree about what the list of kinds is. One of them should be derived
from the other.
The staged version is real. Portfolios need the polymorphic link and the theme rule and
nothing else. Reports need those plus a decision about a computed document. Emails need a
boundary drawn with
notifications. Doing them in that order means each answer is checkedby something shipping, rather than all of it being designed at once.
Done in
29b85e0a— stage one of the three this issue proposes, with stages twoand three filed as #162. Taking your own advice:
Two of the prerequisites turned out to be already built
Worth saying first, because it changed what was left:
point at whatever it copied, both generic. The issue text describes two nullable foreign
keys; that was true when it was written and is not any more.
and keeps a pair that cannot work out of the menu rather than refusing it after the export
button.
So what actually remained was the vocabulary, the first kind, and two
isinstancecalls.DocumentKindwas doing two jobs — one derives from the other nowThe values stay in the enumeration, because they are stored, exported and read by the
API, and a value in a database column is not a thing to compute. Everything said about a
kind — its label, whether Postulo composes one or it only ever arrives as a file, which
theme vocabulary sets it, who provides it — comes from
documents/kinds.py.The two
kindcolumns take their choices from it through a callable, so a kind a pluginregisters reaches every picker and every store's per-kind connection switch with no
migration. There is a test that registers
acme:briefand finds it on the upload form.That is what makes a kind is a plugin something other than a phrase.
Two tests hold the two vocabularies together in both directions: every storable value is
described, and every described key is either storable or claims a provider.
Portfolios: one model, not two
You put the choice in the issue yourself —
— and the answer is one kind, and the themes do the rest. A portfolio is a selection
from the career record with its own layout; it uses every line of
CVItem's machineryunchanged. Two near-identical models would have been two forms, two lists, two exporters and
two of every future change.
The precedent was already here:
CoverLetterhas carried four shapes told apart by a fieldsince it arrived.
CVnow carries two.What genuinely differs is the order of the argument, which is the one thing a document's
structure is for. So a portfolio gets its own base template rather than a flag inside the
CV one: the work first, each piece with what it points at underneath, and the career
following as a compact context row. Both shipped themes gained a
portfolio.html, which#132's rule requires and a test enforces.
Stores stopped asking what they were holding
Deliberately, then. The two remaining
isinstance(document, RenderedDocument)calls aregone; a holder declares
download_url_name,archive_originandarchived_at, andstores.pyasks the document. The test describes a classstores.pyhas never heard of andgets correct metadata back — so a report, or anything a plugin brings, needs no branch there.
The per-kind switches on a store connection come from the registry too.
What is not here, and why
Reports and emails, as you sequenced them. #162 records what each still needs:
and #56 answered it in advance: nothing is stored, a report is a snapshot of the record at
a moment. What is left is a delivery decision: frozen on Download PDF, or never? Probably
the former, but it should be decided rather than assumed.
#104's transport lock, and the possibility that the smallest honest answer is no email
kind at all — a follow-up note is already a
LetterKindand is an email in all butname, and what is worth keeping is what
RenderedDocumentalready does.And the wide contract is deliberately not built. A kind declaring a model, a template per
theme, a starter, how it renders and how it snapshots would be a much larger surface than any
other plugin kind here — and most of it already lives somewhere better: a theme declares its
own templates, a
LETTER_STARTERS-shaped map declares what a new one starts as, renderinggoes through
documents.rendering. Building the rest before anything outside asks for itwould be designing against a guess. The module docstring says so rather than leaving it to be
inferred.
One thing found by accident
A test could be handed a translated string depending on which test ran before it.
LocaleMiddlewareactivates a language per request and nothing deactivated it, so a testsigning in as somebody reading Postulo in Portuguese left Portuguese active for the rest of
the process. Passing alone, failing in company, failing differently by order — the same shape
as the sign-in limiter and the column-width teardown. Every test starts in the instance's own
language now.
Also
tests/test_document_kinds.py(25). Suite 4770 passed, 29 skipped; browser suite 86 passed.Eleven new strings in all 39 European catalogues.
FORMAT_VERSIONis 14; an archivewritten before it restores every CV as a CV, which is what every one of them was, and a kind
an archive invents does the same. Wiki: CVs and portfolios.
Shipped on
0.3.0, withmainkept level.