Spreadsheet import: deadlines and tags are lost without a note, on listings always and on any row whose date is not read #394
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#394
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, readingcore/csv_import.py, importing a small sheet on an in-memory database, and runningparse_dateon dates written the way several of the offered languages write them.Checked again on 2026-10-01 against
mainat0a5cad587: still present.core/csv_import.pyshifted by a few lines when the currency list moved tocore/currencies.py(#248). The code is now atcore/csv_import.py:984("closes_at": None),:988-994(the listing branch ofperform),:1047-1052(the tags),:265-272(the aliases that mean draft) and:894("Status in the spreadsheet: …") for section 1;:860-865(the two dates inparse_rows, the applied date's note at:862-863, the deadline at:865) for section 2;:537-557(_MONTHS),:587-597(the month-name branch ofparse_date) and:1024-1038("Applied on an unknown date") for section 3; and:812-830(ParsedRow.becomes, its last line at:830) and:796(the default status) for the help text. The templates andjobs/closing.pyhave not moved, and the same sheet and the same dates give the same results.Related to #222 (closed), which made the status decide what a row becomes and kept "the status, the channel, the tags and the deadline" for rows that become applications. This issue adds the three places where a deadline or the tags are still dropped: rows that become listings, deadlines
parse_datecannot read, and the month namesparse_datedoes not know. In all three the import succeeds and nothing on the preview, the result page or the record says a value was in the file.1. A row that becomes a listing never keeps its deadline or its tags
core/csv_import.py:989("closes_at": None,inposting_data, for every row);:993-999(the listing branch ofperform, which uses onlyrow.notes);:1052-1057(row.tagsis used on the application path only).:261-268) becomes a listing. For the sheetCompany,Role,Status,Deadline,Tagswith the rowAcme,Dev,Wishlist,2026-10-15,remote; dream, the listing is created withcloses_atempty and an empty description. The same row with the status Applied keeps the deadline on the application and gets both tags.jobs/closing.py:55-66,closing_for, which filters undecided listings oncloses_at) never fires for any imported listing.JobPostinghas no tags, so the tags need somewhere to go.closes_atfromrow.deadline. Since a listing cannot carry tags, add them to the notes that the listing branch already appends to its description (for example "Tags in the spreadsheet: remote, dream", translated, as "Status in the spreadsheet: …" is at:899).2. A deadline that cannot be read leaves no trace
core/csv_import.py:865-870inparse_rows. An unreadable applied date is kept (row.notes.append(f"{sheet.headers[index]}: {value}")at:867-868); the deadline at:870has no such branch. The preview table intemplates/core/import_csv_map.html:74-83has an Applied column and no Deadline column, so the loss cannot be seen before importing either.row.deadline = Noneand the text is gone, for applications and listings alike.parse_datereturnsNonefor a non-empty deadline, append the header and the raw value torow.notes, as the applied date does. Add a Deadline column to the preview.3.
parse_datereads month names in three languages only, and needs a four-digit year beside themcore/csv_import.py:533-553(_MONTHS: English, French and Portuguese) and:583-593(the month-name branch ofparse_date, where the year isnext((n for n in numbers if n > 31), None), so a two-digit year below 32 is never taken as one).parse_datereturnsNone, whichever date order is chosen, for "5 enero 2026", "5 de enero de 2026", "5. Januar 2026", "15. Oktober 2026", "5 gennaio 2026", "5 stycznia 2026", "5 januari 2026", "2026. január 5." and for Excel's own "5-Jan-26". "5 janvier 2026", "5 de março de 2026", "Jan 5, 2026" and numeric forms such as "31.12.2026" all parse. A German sheet whose deadline column reads "15. Oktober 2026" loses every deadline silently (section 2). If its applied column is written the same way, the text survives in the notes but every application arrives with no applied date, recorded as "Applied on an unknown date" (:1029-1043), so the report and Insights do not count it in figures measured from the date sent.MONTHS,MONTHS_3andMONTHS_ALT(django.utils.dates) for every offered language, each undertranslation.override, computed once and cached; keep the hand-written abbreviations that Django does not have ("janv", "juil", "sept"). Accept a two-digit year after a month name when the text holds exactly a day, a month name and one other number.Related: the help text still describes the rule #222 replaced
templates/core/import_csv.html:34and the wiki pageBackups-and-your-data.md:65-66say "Rows with a date applied become applications … rows without one become listings". Since #222 the status decides (ParsedRow.becomes,core/csv_import.py:817-835): a row with no date and the status Rejected becomes an application, and a dated draft row becomes a listing. A row with no status at all is read as applied (ParsedRow.statusdefaults to"applied"at:801andmap_statusnever returns an empty string), so it becomes an application too, and the last line ofbecomes(:835) is never reached. Correct both texts in the same change, and say there what happens to a listing's deadline and tags.Done when
tests/test_csv_import.pyimports a Wishlist row with a deadline and tags and asserts the listing'scloses_atand that the tags appear in its description.parse_datereads "15. Oktober 2026", "5 de enero de 2026", "5 gennaio 2026" and "5-Jan-26" as their dates, and still returnsNonefor "end of October".