Skip to content

Convert remaining 6 partials to JinjaΒ #13566

Closed
6 of 6 issues completed
Closed

Description

@RayBB

Problem / Opportunity

Some partial endpoints still render Templetor templates. Jinja is the target template language. This matters for two audiences. Developers gain more templates in good shape for the FastAPI migration. Contributors gain clean Jinja examples in the codebase. Success means 6 handlers render Jinja with zero Templetor left in their render paths, and parsed page output stays identical.

Proposal

Convert the last 6 partials, one PR per partial, easiest first. Follow AffiliateLinksPartial and these merged reference PRs: #13554 (related subjects), #13556 (publishing history), #13563 (dropper lists). Each PR adds .jinja templates, renders them from the handler, deletes the old Templetor files, and switches ALL callers of each deleted file in the same PR. No behavior change. No new tests.

Parsed DOM sameness is sufficient. Byte sameness is not needed. Two byte differences always occur: djLint puts attributes on separate lines, and quotes encode as " (Jinja) not " (Templetor). Both forms show the same page.

Breakdown

Implementation details for maintainers.

Related files

  • openlibrary/fastapi/partials.py (endpoint routes)
  • openlibrary/plugins/openlibrary/partials.py (handlers)
  • openlibrary/core/jinja.py (shared env, helper rules)
  • Templates per task below.

Full Jinja rule

Each PR must leave zero Templetor in its render path. Convert each nested template and macro too. Shared units give two options: convert the unit and switch ALL its callers in the same PR, or bridge it with render_templetor_template as a stopgap. The per-task notes state the advised option. scripts/check_unused_templates.py must pass.

Playbook

  • Pre-encode JSON in Python with json_encode. Pass the string to the template.
  • |safe matches Templetor $: (raw output). Plain {{ }} matches $ (escaped output).
  • Pass one-off helpers as render kwargs (see core/jinja.py guidance). Do not add globals for one template.
  • The env uses StrictUndefined. Pass each kwarg. Add |default(...) guards where the old template had defaults.
  • Check each JS consumer. dataset decodes entities, so the quote-code difference is safe. But check whether JS parses JSON or uses the raw string. Prove each case.
  • hasattr guards that serve 2 data shapes (Thing and dict) need care in Jinja. Test both shapes.
  • $code blocks that call Solr or the DB must move to Python, or pass as helpers. Templates must not do IO.
  • Test with fixtures: on/off states, empty lists, hostile strings (<script>, &, quotes). Render old and new. Compare the parsed DOM, not raw bytes.
  • Test each endpoint while logged in (dev user openlibrary / openlibrary). Run pre-commit run --files ... on touched files. Run test_templates.py and test_jinja.py.

Tasks (easiest first)

1. ReadingGoalProgressPartial β€” easiest, 3 files. Convert reading_goals/reading_goal_progress.html (29 lines), reading_goals/reading_goal_form.html (24 lines), native_dialog.html (15 lines). The form and the dialog are also used by account/view.html: switch those 2 call sites in the same PR, or bridge them. Endpoint: GET /partials/ReadingGoalProgress.json. Data is only ints and strings. No JSON. No JS contract.

2. BookPageListsPartial β€” easy-medium, 3 files. Convert lists/carousel.html (14 lines) and lists/list_follow.html (82 lines). Bridge (do not convert) follow/follow.html (32 lines): it has 3 other callers (type/user/view.html, account/follows.html, account/view.html). Endpoint: GET /partials/BPListsSection.json?workId=...&editionId=.... The template calls the DB per list (convert_list, get_patron_showcase, get_user_object): move these calls to Python or pass them as helpers.

3. FullTextSuggestionsPartial β€” medium, 3 files. Convert macros/FulltextSearchSuggestion.html (34 lines), macros/FulltextSearchSuggestionItem.html (90 lines), macros/FulltextSuggestionSnippet.html (26 lines). All 3 are dedicated to this partial. Bridge (do not convert) LoadingIndicator (13 lines, 10 other callers). Use the Jinja icon global where the old code calls macros.icon. Endpoint: GET /partials/FulltextSearchSuggestion.json. The handler returns a has_error flag beyond HTML: keep that contract exact.

4. CarouselCardPartial β€” hard, 3 files plus 1 bridge. Convert books/custom_carousel_card.html (101 dense lines), macros/TruncateString.html (3 lines), macros/FormatExpiry.html (10 lines); switch the few other callers of the 2 leaf macros in the same PR. Bridge (do not convert) macros/LoanStatus.html (183 lines, 8 other callers): it deserves its own issue later. Endpoint: GET /partials/CarouselLoadMore.json. Catches: 7 cover fallbacks, loan states, $request.fullpath, plural forms, JSON data attributes. hasattr guards serve 2 data shapes: test both. The card template is shared with books/custom_carousel.html. The handler renders it N times per request. Do this task before task 5.

5. LazyCarouselPartial β€” very hard, 3 files. Convert macros/RawQueryCarousel.html (76 lines) and books/custom_carousel.html (58 lines). Reuse the card from task 4 (do task 4 first). Bridge (do not convert) LoadingIndicator. Switch the caller: macros/QueryCarousel.html (Templetor) must call the new Jinja macro through render_jinja_template; that file stays Templetor (out of scope). Endpoint: GET /partials/LazyCarousel.json. The macro serves all site carousels, plus a cached path. The template rewrites its own query (safe_mode): move that to Python. The template calls Solr when data is absent: move that to Python.

6. SearchFacetsPartial β€” hardest, 2 files, do last. Convert search/work_search_facets.html (91 lines) and search/work_search_selected_facets.html (113 lines). No nested templates. Sub-$def blocks become Jinja macros in the same file. Endpoint: GET /partials/SearchFacets.json. Helper closures (add_facet_url, del_facet_url, build_other_chips) must move to Python: pass changequery, get_facet_map, and friends as render kwargs. Triple return contract: sidebar, title, active facets. Title needs entity_decode for document.title. JS reads 3 data attributes. The templates also serve the search page itself, not only the API. Consider one PR per template.

Requirements Checklist

  • 6 handlers render Jinja. Zero Templetor remains in these 6 render paths (bridged shared units excepted, listed above).
  • No behavior change. Parsed DOM stays identical.
  • Existing tests pass (make test-py-uv). No new tests.
  • Pre-commit passes on each PR (djLint, ruff, mypy, generate-pot, missing-i18n, unused-templates).

Stakeholders

Lead: @RayBB. The 6 tasks suit contributors after the 3 merged reference PRs above. Tasks 1-3 fit newer contributors. Tasks 4-6 need staff review (blast radius: carousels, search).

Instructions for Contributors

Activity

  1. added
    Type: EpicA feature or refactor that is big enough to require subissues. [managed]
    Theme: DevelopmentIssues related to the developer experience and the dev environment. [managed]
    Needs: HelpIssues, typically substantial ones, that need a dedicated developer to take them on. [managed]
    Lead: @RayBBIssues overseen by Ray (Onboarding & Documentation Lead) [manages]
    on Sep 6, 2026
  2. openlibrary-bot commented on Sep 6, 2026

    @openlibrary-bot
    Collaborator

    Context


    Full triage checklist
    • Triage β€” correct labels applied or removed
    • Problem / opportunity β€” problem is clear, audience identified, actionable
    • Scope β€” issue is well-scoped; does not try to do too many things
    • Justification β€” reasoning and impact stated; sufficient for leads to prioritize
    • Success & testing criteria β€” defines what "done" looks like, verifiable by a contributor
    • Proposal & breakdown β€” concrete plan present; checklist actionable for a contributor
    • If bug: environment & repro steps β€” not applicable, this is a refactor epic, not a bug
    • Risks, concerns, open questions β€” identified and surfaced (per-task catches and shared-file bridge decisions)
    • Richy context β€” relevant code, commits, PRs reviewed; no duplicate found (breakdown already exists as linked sub-issues)
    • References β€” not applicable, no design/screenshot references needed for a template-parity refactor

    Note

    This comment was automatically generated by Richy, Open Library's issue enrichment assistant, on behalf of @mekarpeles. This is NOT a sign-off. This issue needs lead review before work begins.

  3. self-assigned this
    on Sep 16, 2026
  4. RayBB commented on Sep 16, 2026

    @RayBB
    CollaboratorAuthor

    Nice work everyone!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Affects: DevelopersLead: @RayBBIssues overseen by Ray (Onboarding & Documentation Lead) [manages]Needs: HelpIssues, typically substantial ones, that need a dedicated developer to take them on. [managed]Theme: DevelopmentIssues related to the developer experience and the dev environment. [managed]Type: EpicA feature or refactor that is big enough to require subissues. [managed]

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions