Skip to content

Convert LazyCarousel partial to Jinja - #13590

Merged
RayBB merged 6 commits into
internetarchive:masterfrom
gauranshahuja:13571/refactor/lazycarousel-jinja
Sep 12, 2026
Merged

RayBB merged 6 commits into
internetarchive:masterfrom
gauranshahuja:13571/refactor/lazycarousel-jinja

Conversation

@gauranshahuja

Copy link
Copy Markdown
Contributor

Closes #13571

Refactor: converts the LazyCarousel partial (macros/RawQueryCarousel.html and books/custom_carousel.html, the carousel every site carousel renders through) from Templetor to Jinja, following #13563 / #13577 and the epic #13566 playbook. No behavior change: the parsed DOM is identical.

Draft until #13578 (CarouselCard) lands: the card is still the Templetor custom_carousel_card, bridged from Python in one place (_render_carousel_card), which then becomes a call to that PR's render_carousel_card().

Technical

  • New openlibrary/macros/RawQueryCarousel.html.jinja and openlibrary/templates/books/custom_carousel.html.jinja, ports of the two Templetor files (deleted; scripts/check_unused_templates.py passes). The templates are now only markup: the logic they carried moved to Python in openlibrary/plugins/openlibrary/partials.py, next to LazyCarouselPartial:
    • render_query_carousel() (was the RawQueryCarousel macro) owns the safe_mode query rewrite, the lazy-load data-config JSON, the /search? URL, the Solr call when no books_data was pre-fetched (gather_lazy_carousel_data, unchanged) and the load_more config. The template does not do IO any more.
    • render_book_carousel() (was books/custom_carousel.html) owns the min_books / test gate, the carousel data-config JSON, and the per-book shape resolution (first edition from a list or a docs dict, Thing kept as-is vs dict → web.storage, authors and loan from the work record), verbatim from the template.
    • Both are @public, the way Convert SearchFacets partial to Jinja #13577's render_search_facets() is, so the Templetor pages that embed carousels call them directly: macros/QueryCarousel.html (stays Templetor, as the issue says), home/index.html, account/loans.html, account/mybooks.html. All four callers of the deleted custom_carousel.html are switched in this PR.
  • The cached path: QueryCarousel.html's use_cache branch called CacheableMacro("RawQueryCarousel", ...), which memoized the Templetor macro through render_cached_macro. It now calls render_cached_query_carousel(), which memoizes render_query_carousel with the same key prefix (macro name + language, print-disabled, sfw, bot) and the same five minute timeout. To share that, the prefix builder is extracted from render_cached_macro into cached_macro_key_prefix() in plugins/upstream/utils.py (with CACHED_MACRO_TIMEOUT); render_cached_macro itself is unchanged in behaviour and keeps its test. macros/CacheableMacro.html had no other caller and is deleted.
  • LoadingIndicator is bridged (not converted), rendered from Python through render_macro like Convert CarouselCard partial to Jinja (#13570) #13578 does for LoanStatus.
  • LazyCarouselPartial.generate_async() calls render_query_carousel(..., lazy=False, books_data=...) instead of render_macro("RawQueryCarousel", ...); the {"partials": html} contract is unchanged. public(gather_lazy_carousel_data) is dropped: its only template caller was the deleted macro.
  • All _() msgids are unchanged; messages.pot only changes location comments (Loading carousel and Loading... are now extracted from partials.py).

One thing I kept as-is rather than fix, since this is a pure engine swap: QueryCarousel.html's use_cache and not lazy branch does not pass lazy through, so the cached render is the lazy placeholder (the macro's default). That is what it did before too. Happy to fix in a follow-up if wanted.

Testing

Rendered the old Templetor macro/template (from master) and the new Python + Jinja path with the same fixtures, and compared the parsed DOM (tags, decoded attribute values, text nodes), not bytes. 17 scenarios, all identical:

  • custom_carousel: no books (renders nothing), no books with test, fewer than min_books, 10 mixed books (Solr docs with editions as a list / a docs dict / an empty docs list / no editions, a Thing-like record with .key, a hostile <script> & "quotes" title, a loan, authors) in compact mode with a full load_more, grid layout with secondary_action, title without url, load_more={} and a partial load_more. The card was stubbed on both sides to serialise exactly what it is handed, so the edition/Thing/storage resolution, lazy past the sixth card, authors and loan are compared too.
  • RawQueryCarousel: lazy placeholder minimal / with every option / fallback=True / title only; eager with pre-fetched books and the search form; eager without a url (the /search? urlencode path) in grid layout; eager with no books_data (the Solr call, stubbed to echo its arguments into the output, so query, sort, limit, has_fulltext_only, safe_mode are compared); eager with no results, with and without the search form.

Byte-level differences only: djlint attribute layout and &#34; vs &quot;, as the epic describes. lazy-carousel.js and carousel.js read data-config through dataset and JSON.parse, so the quote encoding is safe.

djlint --lint / --reformat (v1.43.2), ruff, mypy, scripts/check_unused_templates.py, scripts/detect_missing_i18n.py: clean. test_templates.py, test_jinja.py, test_partials.py, test_home.py, upstream/tests/test_utils.py: same results as master (the test_all_jinja_templates_render_valid_html subtest and the three test_home templates fail on my Windows checkout on master as well; environment, not this change).

Endpoints to check on a dev instance: GET /partials/LazyCarousel.json?query=subject:romance&title=Romance&key=romance (the lazy-loaded carousels on the home page) and the home page itself, which renders the non-lazy "Books We Love" / "Recently Returned" carousels through render_book_carousel.

I have not run make test-py-uv in Docker locally; relying on CI for the full run.

Screenshot

N/A — the partial's HTML is DOM-identical (see Testing).

Stakeholders

@RayBB

@RayBB RayBB self-assigned this Sep 10, 2026
@github-project-automation github-project-automation Bot moved this to Waiting Review/Merge from Staff in Ray's Project Sep 10, 2026
@RayBB

RayBB commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

@gauranshahuja please rebase and resolve conflicts

@gauranshahuja
gauranshahuja force-pushed the 13571/refactor/lazycarousel-jinja branch from c3e5d03 to be82d15 Compare September 11, 2026 10:57
@gauranshahuja

Copy link
Copy Markdown
Contributor Author

Rebased onto master now that #13578 has merged, conflicts resolved, CI green.

Two things changed beyond the plain rebase:

  • The temporary card bridge is gone. _render_carousel_card() now calls Convert CarouselCard partial to Jinja #13570's get_carousel_card_data() and renders books/custom_carousel_card.html.jinja — the same pair CarouselCardPartial uses for load-more cards — instead of the Templetor books/custom_carousel_card.
  • books/custom_carousel.html is deleted rather than carrying master's switch to the Jinja card, since render_book_carousel() builds the cards in Python now.

LoadingIndicator is still bridged as Templetor, as before.

Taking it out of draft.

@gauranshahuja
gauranshahuja marked this pull request as ready for review September 11, 2026 11:02

@RayBB RayBB left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please try to simplify your changes and follow the patters now done in the other PRs? For example, one method to gather the data to be passed to the template instead of a function that directly renders the template.

Trying to give quick feedback before I look in depth soon

@github-project-automation github-project-automation Bot moved this from Waiting Review/Merge from Staff to Someone else is working on it in Ray's Project Sep 11, 2026
@gauranshahuja
gauranshahuja force-pushed the 13571/refactor/lazycarousel-jinja branch from be82d15 to 15be594 Compare September 11, 2026 11:46
@RayBB

RayBB commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Also, a few more conflicts popped up to resolve

@gauranshahuja
gauranshahuja force-pushed the 13571/refactor/lazycarousel-jinja branch 2 times, most recently from df660e1 to 1de83c9 Compare September 11, 2026 18:59
@gauranshahuja
gauranshahuja force-pushed the 13571/refactor/lazycarousel-jinja branch from 1de83c9 to a44e38a Compare September 11, 2026 19:15
@RayBB
RayBB self-requested a review September 12, 2026 00:54
Shows one book carousel and shows a carousel for a query now use a macro.

- Move book carousel HTML into macro book_carousel(show, title, url, key, grid, compact, loadjs, config_json, cards) in books/custom_carousel.html.jinja. Keep direct render with guard 'if show is defined' so Templetor callers still work.
- Change RawQueryCarousel.html.jinja to import the macro and call it with explicit arguments. Remove shared-scope include. This prevents outer scope pollution.
- Shorten headers to one line per jinja.md best practice.

This follows docs/wiki/developers/frontend/jinja.md: use a macro for a reusable piece, do not pollute outer scope, keep macro files short.
Fix 1: Add per-card try/except in get_book_carousel_data. One bad card now does not stop the full carousel. This matches 13578.

Fix 2: Add full_path input to get_book_carousel_data, get_query_carousel_data and LazyCarouselPartial.generate_async. FastAPI now passes full_path with Depends(get_fullpath). Templetor callers now pass web.ctx.fullpath explicitly. Remove use of global web.ctx.fullpath inside the helper. This makes the dependency clear and testable. This matches 13578.

Update callers: home/index.html, account/loans.html, account/mybooks.html, macros/QueryCarousel.html.
…arams

- Rename partials.get_carousel_data to get_book_carousel_data so it no
  longer collides with openlibrary.core.carousels.get_carousel_data,
  which is a different function with a different signature.
- Remove the no-op use_cache and lazy params from QueryCarousel and
  ListCarousel: the cached-eager path they selected was removed when the
  partial moved to Jinja, so both params have been ignored.
- Drop the now-meaningless use_cache=False at the one caller that set it.

@RayBB RayBB left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one really was a sprawling one. You really gave me a PR in good shape so nice work with that. I did end up pushing some more refactoring to clean it up but that's it.

Thanks a lot for your work you're doing great!

I tested this pretty well locally and on testing by using various carousels. There are whitespace differences (in the html) but that's it.

Would you like to take on #13569 if so just leave a comment there.

@RayBB
RayBB merged commit 02c5a2f into internetarchive:master Sep 12, 2026
3 checks passed
@github-project-automation github-project-automation Bot moved this from Someone else is working on it to Done in Ray's Project Sep 12, 2026
lokesh added a commit to Sravan1011/openlibrary that referenced this pull request Sep 22, 2026
Port NearbyBooksPartial onto the Jinja carousel (RawQueryCarousel and
custom_carousel were converted upstream in internetarchive#13590).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Convert LazyCarousel partial to Jinja

2 participants