Your career: deleting an entry that is on a CV leaves a dangling row that makes the CV's page answer 500 #381
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#381
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, by reading the CV entry model and rendering code and then deleting a career entry in a test client.Checked again on 2026-10-01 against
mainat0a5cad587: still present. The code is now atdocuments/models.py:315-316(CVItem.__str__),documents/rendering.py:133-145(build_sections) anddocuments/rendering.py:310(the skip in_section_blocks);resume/models.py,resume/views.py, the templates andapi/routers/documents.pyhave not moved.A CV entry (
CVItem) points at its career entry through a generic foreign key, and nothing removes it when that career entry is deleted. The row left behind cannot be printed: its__str__returns a lazy translation proxy, whichstr()refuses. From then on the CV's page, the entry's Tailor and Remove pages, and the API's CV detail all answer 500, so the person has no way in the interface to remove the broken row. The PDF still exports, but with an empty heading where the job was.documents/models.py:275-276,CVItem.__str__:return str(self.item) if self.item else _("Missing entry"), where_isgettext_lazy.resume/models.py:33-52,ResumeItem: the only reverse relation istranslations = GenericRelation("resume.Translation"); none todocuments.CVItem, and nopost_deletereceiver for any career model indocuments/signals.pyor elsewhere.resume/views.py:301-309,ResumeItemDeleteView: a plainDeleteView.templates/documents/cv_detail.html:104({{ item }}),templates/documents/cv_item_form.html:11andtemplates/partials/confirm_delete.html:11(blocktranslate with name=object),api/routers/documents.py:43("label": str(item)).documents/rendering.py:159-171,build_sections: wraps theNoneentry in anEntryinstead of skipping it;_section_blocks(rendering.py:336) already skipsentry.item is Nonefor the text formats.CVItemrow stays.GET /documents/cvs/<id>/,/documents/cv-entries/<id>/edit/and/documents/cv-entries/<id>/delete/then raiseTypeError: __str__ returned non-string (type __proxy__);GET /api/v1/cvs/<id>builds the samestr(item)(so the MCP server's CV tool fails too).render_cv_htmlproduces an Experience section holding<h3 class="role"></h3>and<p class="org"><bdi></bdi></p>, which is what Export PDF sends to an employer.cv_entries = GenericRelation("documents.CVItem")onResumeItembesidetranslations, so deleting a career entry cascades to its CV rows (a schema-free change).CVItem.__str__returnstr(_("Missing entry")).Noneinbuild_sections, as_section_blocksalready does.CVItemrows whose target no longer exists.Done when
tests/test_documents.pydeletes anExperiencethat is on a CV throughresume:item_deleteand asserts that the CV has no entries left and thatdocuments:cv_detailanswers 200; it fails today with theTypeError.str()of aCVItemwhose target is gone returns astr, and thatbuild_sectionsleaves such an entry out.