Background worker: errands run in the server's language and time zone, not their owner's #383
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#383
Loading…
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?
Found on 2026-09-29 in a read-only review of postulo at
95348908d, following whaterrands.performsets up before it calls a handler, with and withoutPOSTULO_BACKGROUND_WORK, and running areport_pdferrand the way the worker does, with nothing activated.Checked again on 2026-10-01 against
mainat0a5cad587: still present. The code is now atcore/site.py:207-210(default_time_zone),applications/views.py:1239(ReportPDFView.post), and indocuments/rendering.pyat:429(the{{ date }}placeholder),:536-549(sent_to, called at:646and:709) and:682-684(snapshot_report); every other location below is unchanged.Related to #335, which reports the same defect for a scheduled sync and asks for one "as this person" helper; this issue is the errand side of it and should use that helper. Related to #340, whose point 5 proposes a chosen language for the report "whoever draws it"; it does not cover the time zone or the other handlers. #223 (closed) made notifications and documents follow the recipient's language, but predates the worker (#247), and the errand path does not use the helper it introduced.
UserPreferencesMiddlewareactivates the signed-in person's language and time zone for every request. An errand done in the request inherits them. An errand done bymanage.py workruns with neither, so it words everything inLANGUAGE_CODE(en-gb) and computes every date insettings.TIME_ZONE(POSTULO_TIME_ZONE,Europe/Parisby default), ignoring the person's profile and the defaults an administrator chose in Server settings. A filed report can cover the wrong period and miss applications, a frozen letter can carry the wrong day, and the errand page and the timeline are written in a language the person did not choose. How the operator deployed the instance decides all of it.core/errands.py:116-151(perform):work.run(errand)is wrapped inlogs.request_scopeonly; notranslation.override, notimezone.override.core/tasks.py:19-23(perform_errand) andcore/management/commands/work.pyactivate nothing either.core/middleware.py:123-143(UserPreferencesMiddleware.__call__): the only place the person's zone (Profile.time_zone, elsesite.default_time_zone(),core/site.py:245-248) and language are activated, per request.notifications/service.py:18-30(language_for) and:49-50: the notifier handler (notifications/slow.py:46-54) already works round the gap insidenotify. No other handler does.1. The filed report covers the server's period and is worded in English
applications/slow.py:32-39(render_a_report);applications/views.py:1199(ReportPDFView.postsends the raw query string, not a resolved period);applications/reports.py:198(period_from:today = today or timezone.localdate()),:114-129(Period.bounds, which usestimezone.get_current_timezone()),:434,:453,:460and:477(localdateandlocaltimefor the evidence rows and Produced on);documents/rendering.py:708-710(snapshot_reportrecordstranslation.get_language()).Europe/Paris, the worker profile ofdocker/compose.ymlis on, and a person reads Postulo in French with the profile zoneAmerica/New_York.?on=2026-09, an application sent at 22:30 that evening is on the page and missing from the filed PDF, becausePeriod.boundsputs the end of September at Paris midnight. Produced on and the evidence dates are Paris dates. Running the errand as the worker does gave exactly this:en-gb, the title "Job search report · September 2026", and no row for the application, whilereports.buildunder the person's zone listed it.LANGUAGE_CODE, andsnapshot_reportrecords the document as English.2. A frozen letter carries the server's date
documents/slow.py:57-132(freeze_what_was_sent), the only path that freezes a letter, throughsnapshot_letter;documents/rendering.py:455(the{{ date }}placeholder:formats.date_format(timezone.localdate(), "DATE_FORMAT"));templates/documents/themes/base_letter.html:37({% now "j F Y" %}).render_letter_htmloverrides the language; only the day is wrong.3. Stored sentences and timeline text are in
LANGUAGE_CODEcore/errands.py:130,:140(error=str(refusal), where a lazyRefused(_(...))is resolved) and:148.core/slow.py:64;documents/slow.py:40,:48-52,:81and:130;jobs/slow.py:66,:90,:116,:124,:140,:149and:153;applications/slow.py:39and:51-53.core/models.py:775-780(Errand.messagereturns the stored string) andtemplates/core/partials/errand_state.html:24and:34, which print it.documents/slow.py:123-127:record_event(summary=str(_("Documents sent"))).documents/rendering.py:562-575(sent_to), called at:672and:735outside the document-language override.working_labelis resolved when the page renders, and under it "Your data is ready to download.", "Recorded what you sent." or "Nothing resembling a job posting was found on that page." in English. The timeline entry "Documents sent" is written in English and stays so, although the catalogues have it.RenderedDocument.sent_tostores " at " in English, where the same action without a worker stores it in the person's language. CV and letter bodies are not affected:documents/rendering.pyoverrides to the document's language (#223).en-gbmeets this on every slow action once the operator turns the worker on, and part of it is written into a timeline they keep for months. It breaks the module's own promise, in the docstring ofcore/errands.py, that "there is no path here that only runs on somebody else's machine".4. The comments and the wiki say otherwise
applications/slow.py:35-37says the language "is the worker's, which is the instance default". It isLANGUAGE_CODE, whatever Server settings → Defaults says.documents/rendering.py:708-710says the language recorded for a report "is the request's (#283)". With a worker there is no request.Files-and-what-you-sent.md:72-73in postulo.wiki at de1f52467) says a report is kept "in the language you were reading Postulo in when you pressed Download". With a worker it is not.Fix
core: the profile's zone, elsesite.default_time_zone(), else the environment, with an unknown zone name falling back rather than raising; the profile's language when the instance still offers it, elsesite.default_language().language_forinnotifications/service.pymoves there too.UserPreferencesMiddleware,notifyand the sync of #335 use the same helper.errands.perform, runwork.run(errand)and every_finishsentence, including the one at line 130, insidetranslation.override(...)andtimezone.override(...)forerrand.owner. The inline path and the queued path then agree, and the report follows its owner until #340 gives it a language of its own.ReportPDFView.post, put the resolved period in the payload (reports.as_query(reports.period_from(request.GET))), so that neither the worker's zone nor a queue that crosses midnight chooses it.documents/archiving.py:108(send_copy), which storeslast_errorsentences per owner with nothing activated, can use the same helper.Done when
tests/test_errands.py: a handler that returns a translated sentence, and one that raises a lazyRefused, run througherrands.performwith nothing activated for an owner whose profile language is French; the stored message and error are the French ones. It fails today.tests/test_slow_work.py: areport_pdferrand performed with nothing activated, for a profile inAmerica/New_Yorkreading French, at a moment when that zone andsettings.TIME_ZONEare on different dates, files a report in French with the period and the evidence rows the report page shows that person.tests/test_errands.py: asent_documentserrand performed the same way, with the clock frozen where the two zones differ by a day, freezes a letter whose{{ date }}and{% now %}line are the owner's local date, and writes the event summary andsent_toin the owner's language.