Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions openlibrary/fastapi/partials.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,15 @@ async def search_facets_partial(
@router.get("/partials/AffiliateLinks.json", include_in_schema=SHOW_PARTIALS_IN_SCHEMA)
async def affiliate_links_partial(
data: Annotated[str, Query(description="JSON-encoded data with book information")],
jinja: Annotated[bool, Query(description="Use Jinja2 template instead of Templetor")] = False,
) -> dict:
"""
Get affiliate links HTML for a book.

The data parameter should contain:
- args: list with [title, opts] where opts is a dict with optional isbn

Optionally pass jinja=true to render with the Jinja2 template (for testing).
"""
try:
parsed_data = json.loads(data)
Expand All @@ -76,6 +79,7 @@ async def affiliate_links_partial(
isbn=opts.get("isbn", None),
asin=opts.get("asin", None),
prices=opts.get("prices", False),
jinja=jinja,
)


Expand Down
33 changes: 33 additions & 0 deletions openlibrary/macros/AffiliateLinks.html.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{% macro affiliate_link(store) %}
<li class="prices-{{ store.key }}">
Comment thread
Sanket17052006 marked this conversation as resolved.
Comment thread
Sanket17052006 marked this conversation as resolved.
Comment thread
Sanket17052006 marked this conversation as resolved.
Comment thread
Sanket17052006 marked this conversation as resolved.
Comment thread
Sanket17052006 marked this conversation as resolved.
<a href="{{ store.link }}"
title="{{ _('Look for this edition for sale at %(store)s', store=store.name) }}"
data-ol-link-track="BuyLink|{{ store.analytics_key }}"
target="_blank" rel="noopener noreferrer">{{ store.name }}</a>
{% if store.price %}
<br>
<span name="price">{{ store.price }}{{ store.price_note }}</span>
{% endif %}
</li>
{% endmacro %}

<span class="affiliate-links-section">
<ul class="buy-options-table">
Comment thread
Sanket17052006 marked this conversation as resolved.
Comment thread
Sanket17052006 marked this conversation as resolved.
Comment thread
Sanket17052006 marked this conversation as resolved.
Comment thread
Sanket17052006 marked this conversation as resolved.
Comment thread
Sanket17052006 marked this conversation as resolved.
{% for store in primary_stores %}
{{ affiliate_link(store) | safe }}
{% endfor %}
{% if more_stores %}
<li class="more">
<details>
<summary>{{ _('More') }}</summary>
<ul>
{% for store in more_stores %}
{{ affiliate_link(store) | safe }}
{% endfor %}
</ul>
</details>
</li>
{% endif %}
</ul>
<small>{{ _('When you buy books using these links the Internet Archive may earn a <a class="nostyle" href="/help/faq/about#selling">small commission</a>.') | safe }}</small>
</span>
48 changes: 45 additions & 3 deletions openlibrary/plugins/openlibrary/partials.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from dataclasses import dataclass
from functools import cache as functools_cache
from hashlib import md5
from pathlib import Path
from typing import Literal, NotRequired, TypedDict
from urllib.parse import parse_qs, quote, quote_plus

Expand All @@ -19,7 +21,6 @@
get_betterworldbooks_metadata,
)
from openlibrary.i18n import gettext as _
from openlibrary.plugins.openlibrary.code import is_bot
from openlibrary.plugins.openlibrary.lists import get_lists_async, get_user_lists
from openlibrary.plugins.upstream.utils import render_macro
from openlibrary.plugins.upstream.yearly_reading_goals import get_reading_goals
Expand Down Expand Up @@ -292,6 +293,38 @@ def build_more_stores(ctx: AffiliateStoreBuildContext) -> list[AffiliateStore]:
]


@functools_cache
def _get_jinja_env():
"""Lazily initialize and return the Jinja2 environment (cached after first call).

Per Jinja docs, a single Environment instance should be reused to take
advantage of template compilation caching.
"""
from jinja2 import Environment, FileSystemLoader, StrictUndefined

env = Environment(
loader=FileSystemLoader(Path(__file__).resolve().parent.parent.parent / "macros"),
autoescape=True,
undefined=StrictUndefined,
trim_blocks=True,
lstrip_blocks=True,
)
env.globals["_"] = _
return env


def _render_affiliate_links_jinja(
primary_stores: list[AffiliateStore],
more_stores: list[AffiliateStore],
) -> str:
"""Render affiliate links using Jinja2."""
template = _get_jinja_env().get_template("AffiliateLinks.html.jinja")
return template.render(
primary_stores=primary_stores,
more_stores=more_stores,
)


class AffiliateLinksPartial:
"""Handler for affiliate links"""

Expand All @@ -301,7 +334,10 @@ async def generate_async(
isbn: str | None,
asin: str | None,
prices: bool,
jinja: bool = False,
) -> dict:
from openlibrary.plugins.openlibrary.code import is_bot

bwb_metadata = None
amz_metadata = None
should_fetch_prices = not is_bot() and prices
Expand All @@ -317,8 +353,14 @@ async def generate_async(

primary_stores = build_primary_stores(ctx)
more_stores = build_more_stores(ctx)
macro = web.template.Template.globals["macros"].AffiliateLinks(primary_stores, more_stores)
return {"partials": str(macro)}

if jinja:
html = _render_affiliate_links_jinja(primary_stores, more_stores)
else:
macro = web.template.Template.globals["macros"].AffiliateLinks(primary_stores, more_stores)
html = str(macro)

return {"partials": html}


class SearchFacetsPartial:
Expand Down
Loading