-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Refactor: Unify book provider download options template #11615
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Refactor: Unify book provider download options template #11615
Conversation
for more information, see https://pre-commit.ci
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR refactors the book provider download options rendering by consolidating multiple provider-specific templates into a single unified template that works with the Acquisition dataclass. The refactor aims to reduce code duplication and centralize download option definitions within each provider's get_acquisitions() method.
Key Changes:
- Added a unified
book_providers/download_options.htmltemplate that renders acquisitions generically - Extended the
Acquisitiondataclass with an optionallabelfield to preserve UI text - Updated 8 book providers (CitaPress, OpenStax, Project Gutenberg, Project Runeberg, Standard Ebooks, LibriVox, Wikisource, Internet Archive) to return complete acquisition lists with labels
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
openlibrary/book_providers.py |
Added label field to Acquisition dataclass; updated from_json and from_opds_json to populate labels; refactored render_download_options in AbstractBookProvider and InternetArchiveProvider; updated all 8 providers' get_acquisitions() methods to return complete acquisition lists with labels and proper URLs |
openlibrary/templates/book_providers/download_options.html |
New unified template that iterates over acquisitions and renders download links with fallback label logic |
openlibrary/templates/book_providers/wikisource_download_options.html |
Deleted provider-specific template (replaced by unified template) |
openlibrary/templates/book_providers/standard_ebooks_download_options.html |
Deleted provider-specific template (replaced by unified template) |
openlibrary/templates/book_providers/runeberg_download_options.html |
Deleted provider-specific template (replaced by unified template) |
openlibrary/templates/book_providers/openstax_download_options.html |
Deleted provider-specific template (replaced by unified template) |
openlibrary/templates/book_providers/librivox_download_options.html |
Deleted provider-specific template (replaced by unified template) |
openlibrary/templates/book_providers/ia_download_options.html |
Deleted provider-specific template (replaced by unified template) |
openlibrary/templates/book_providers/gutenberg_download_options.html |
Deleted provider-specific template (replaced by unified template) |
openlibrary/templates/book_providers/cita_press_download_options.html |
Deleted provider-specific template (replaced by unified template) |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return [ | ||
| Acquisition( | ||
| access='open-access', | ||
| format='web', | ||
| price=None, | ||
| url=f'https://runeberg.org/{self.get_best_identifier(ed_or_solr)}/', | ||
| url=f'https://runeberg.org/{runeberg_id}.zip', | ||
| provider_name=self.short_name, | ||
| ) | ||
| label='Scanned images', | ||
| ), | ||
| Acquisition( | ||
| access='open-access', | ||
| format='web', | ||
| price=None, | ||
| url=f'https://runeberg.org/download.pl?mode=jpgzip&work={runeberg_id}', | ||
| provider_name=self.short_name, | ||
| label='Color images', | ||
| ), | ||
| Acquisition( | ||
| access='open-access', | ||
| format='web', | ||
| price=None, | ||
| url=f'https://runeberg.org/download.pl?mode=html&work={runeberg_id}', | ||
| provider_name=self.short_name, | ||
| label='HTML', | ||
| ), | ||
| Acquisition( | ||
| access='open-access', | ||
| format='web', | ||
| price=None, | ||
| url=f'https://runeberg.org/download.pl?mode=txtzip&work={runeberg_id}', | ||
| provider_name=self.short_name, | ||
| label='Text and index files', | ||
| ), | ||
| Acquisition( | ||
| access='open-access', | ||
| format='web', | ||
| price=None, | ||
| url=f'https://runeberg.org/download.pl?mode=ocrtext&work={runeberg_id}', | ||
| provider_name=self.short_name, | ||
| label='OCR text', | ||
| ), | ||
| Acquisition( | ||
| access='open-access', | ||
| format='web', | ||
| price=None, | ||
| url=f'https://runeberg.org/download.pl?mode=work&work={runeberg_id}', | ||
| provider_name=self.short_name, | ||
| label='More at Project Runeberg', | ||
| ), | ||
| ] |
Copilot
AI
Dec 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All acquisitions for Project Runeberg use format='web', even though they represent distinct file types (Scanned images zip, Color images, HTML, Text and index files, OCR text). This makes the format field less useful for tracking and categorization. Consider using more specific format values or adding the format literal types to support these file types.
| acqs.append( | ||
| Acquisition( | ||
| access='open-access', | ||
| format='audio', | ||
| format='web', | ||
| price=None, | ||
| url=f'https://librivox.org/{self.get_best_identifier(ed_or_solr)}', | ||
| url=f'https://librivox.org/rss/{librivox_id}', | ||
| provider_name=self.short_name, | ||
| label='RSS Feed', | ||
| ) | ||
| ] | ||
| ) | ||
| acqs.append( | ||
| Acquisition( | ||
| access='open-access', | ||
| format='web', | ||
| price=None, | ||
| url=f'https://librivox.org/{librivox_id}', | ||
| provider_name=self.short_name, | ||
| label='More at LibriVox', | ||
| ) | ||
| ) | ||
| return acqs |
Copilot
AI
Dec 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The format field for RSS Feed and "More at LibriVox" acquisitions is set to 'web'. While 'web' is appropriate for the "More at" link, an RSS feed is not a web reading interface but a syndication format. This reduces semantic clarity.
openlibrary/book_providers.py
Outdated
| ), | ||
| Acquisition( | ||
| access='open-access', | ||
| format='web', |
Copilot
AI
Dec 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The format field for MOBI (for Kindle) acquisition is set to 'web', but MOBI is a distinct ebook format. Using 'web' for MOBI reduces semantic clarity and makes analytics tracking less precise. This is the same issue as in the Internet Archive provider.
| format='web', | |
| format='mobi', |
| <p class="cta-section-title">$_("Download Options")</p> | ||
| <ul class="ebook-download-options"> | ||
| $for acq in acquisitions: | ||
| <li><a href="$acq.url" data-ol-link-track="Download|$(acq.format)_$(acq.provider_name)" title="$_('Download from') $acq.provider_name">$(get_label(acq))</a></li> |
Copilot
AI
Dec 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The title attribute construction will result in incorrect output when labels contain special formatting. For labels like "More at LibriVox" or "Read at Wikisource", the title will be "Download from librivox" or "Download from wikisource", which is semantically incorrect - these are not downloads but rather links to read at external sites. The title should be more contextually appropriate or conditionally set based on whether it's actually a download link.
| <li><a href="$acq.url" data-ol-link-track="Download|$(acq.format)_$(acq.provider_name)" title="$_('Download from') $acq.provider_name">$(get_label(acq))</a></li> | |
| <li><a href="$acq.url" data-ol-link-track="Download|$(acq.format)_$(acq.provider_name)" title="$if acq.format == 'web':$_('Read at')$else:$_('Download from')$endif $acq.provider_name">$(get_label(acq))</a></li> |
| <p class="cta-section-title">$_("Download Options")</p> | ||
| <ul class="ebook-download-options"> | ||
| $for acq in acquisitions: | ||
| <li><a href="$acq.url" data-ol-link-track="Download|$(acq.format)_$(acq.provider_name)" title="$_('Download from') $acq.provider_name">$(get_label(acq))</a></li> |
Copilot
AI
Dec 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tracking attribute data-ol-link-track also concatenates with provider_name which can be None. When provider_name is None, this will produce invalid tracking data like "Download|pdf_None". The template should handle None values or ensure provider_name is always set.
| <li><a href="$acq.url" data-ol-link-track="Download|$(acq.format)_$(acq.provider_name)" title="$_('Download from') $acq.provider_name">$(get_label(acq))</a></li> | |
| <li><a href="$acq.url" data-ol-link-track="Download|$(acq.format)$('_' + acq.provider_name if acq.provider_name else '')" title="$_('Download from')$(': ' + acq.provider_name if acq.provider_name else '')">$(get_label(acq))</a></li> |
| if txt := edition.get_ia_download_link('_djvu.txt'): | ||
| acqs.append( | ||
| Acquisition( | ||
| access=access, | ||
| format='web', | ||
| price=None, | ||
| url=txt, | ||
| provider_name=self.short_name, | ||
| label='Plain text', | ||
| ) | ||
| ) |
Copilot
AI
Dec 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The format field is set to 'web' for plain text files, but the Acquisition dataclass only allows 'web', 'pdf', 'epub', or 'audio' as valid formats (line 80). Plain text is a distinct ebook format and should likely have its own format type. This inconsistency affects tracking and semantic clarity.
| return [ | ||
| Acquisition( | ||
| access='open-access', | ||
| format='web', | ||
| price=None, | ||
| url=f'https://www.gutenberg.org/ebooks/{self.get_best_identifier(ed_or_solr)}', | ||
| url=url, | ||
| provider_name=self.short_name, | ||
| ) | ||
| label='HTML', | ||
| ), | ||
| Acquisition( | ||
| access='open-access', | ||
| format='web', | ||
| price=None, | ||
| url=url, | ||
| provider_name=self.short_name, | ||
| label='Plain text', | ||
| ), | ||
| Acquisition( | ||
| access='open-access', | ||
| format='epub', | ||
| price=None, | ||
| url=url, | ||
| provider_name=self.short_name, | ||
| label='ePub', | ||
| ), | ||
| Acquisition( | ||
| access='open-access', | ||
| format='web', | ||
| price=None, | ||
| url=url, | ||
| provider_name=self.short_name, | ||
| label='Kindle', | ||
| ), | ||
| Acquisition( | ||
| access='open-access', | ||
| format='web', | ||
| price=None, | ||
| url=url, | ||
| provider_name=self.short_name, | ||
| label='More at Project Gutenberg', | ||
| ), | ||
| ] |
Copilot
AI
Dec 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The format field for multiple acquisitions is set to 'web' when they represent specific downloadable file formats (HTML, Plain text, Kindle). The format 'web' should be reserved for web-based reading or generic landing pages. Using 'web' for distinct file formats reduces the semantic value of the format field and makes analytics tracking less precise.
| Acquisition( | ||
| access='open-access', | ||
| format='web', | ||
| price=None, | ||
| url=f'{base_url}/downloads/{flat_id}.azw3', | ||
| provider_name=self.short_name, | ||
| label='Kindle (azw3)', | ||
| ), | ||
| Acquisition( | ||
| access='open-access', | ||
| format='epub', | ||
| price=None, | ||
| url=f'{base_url}/downloads/{flat_id}.kepub.epub', | ||
| provider_name=self.short_name, | ||
| label='Kobo (kepub)', | ||
| ), | ||
| Acquisition( | ||
| access='open-access', | ||
| format='web', | ||
| price=None, | ||
| url=f'https://github.com/standardebooks/{flat_id}', | ||
| provider_name=self.short_name, | ||
| label='Source Code', | ||
| ), | ||
| Acquisition( | ||
| access='open-access', | ||
| format='web', | ||
| price=None, | ||
| url=base_url, | ||
| provider_name=self.short_name, | ||
| label='More at Standard Ebooks', | ||
| ), | ||
| ] |
Copilot
AI
Dec 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The format field for Kindle (azw3) and Source Code acquisitions is set to 'web', but azw3 is a distinct ebook format. Using 'web' for these reduces semantic clarity and makes analytics tracking less precise. Consider adding support for these format types in the Acquisition literal or use more appropriate existing types.
| <p class="cta-section-title">$_("Download Options")</p> | ||
| <ul class="ebook-download-options"> | ||
| $for acq in acquisitions: | ||
| <li><a href="$acq.url" data-ol-link-track="Download|$(acq.format)_$(acq.provider_name)" title="$_('Download from') $acq.provider_name">$(get_label(acq))</a></li> |
Copilot
AI
Dec 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The title attribute concatenates with provider_name which can be None (as defined in the Acquisition dataclass line 83). When provider_name is None, this will produce a title like "Download from None", which is not user-friendly. The template should handle None values appropriately.
| <li><a href="$acq.url" data-ol-link-track="Download|$(acq.format)_$(acq.provider_name)" title="$_('Download from') $acq.provider_name">$(get_label(acq))</a></li> | |
| $if acq.provider_name: | |
| <li><a href="$acq.url" data-ol-link-track="Download|$(acq.format)_$(acq.provider_name)" title="$_('Download from') $acq.provider_name">$(get_label(acq))</a></li> | |
| $else: | |
| <li><a href="$acq.url" data-ol-link-track="Download|$(acq.format)_$(acq.provider_name)" title="$_('Download')">$(get_label(acq))</a></li> |
| acqs.append( | ||
| Acquisition( | ||
| access=access, | ||
| format='web', | ||
| price=None, | ||
| url=edition.url('/daisy'), | ||
| provider_name=self.short_name, | ||
| label='DAISY', | ||
| ) | ||
| ) |
Copilot
AI
Dec 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The format field is set to 'web' for DAISY files, but the Acquisition dataclass only allows 'web', 'pdf', 'epub', or 'audio' as valid formats (line 80). DAISY is a distinct accessibility-focused ebook format and should likely have its own format type. This inconsistency affects tracking and semantic clarity.
for more information, see https://pre-commit.ci
|
@cdrini Can you review it pls |
🧩 Summary
This PR unifies the rendering of book provider download options by replacing multiple provider-specific templates with a single shared template that renders directly from
get_acquisitions().The goal is to remove duplicated template logic and centralize download option definitions within each provider’s acquisition data.
Fixes #11274
✨ What’s Changed
Unified template
book_providers/download_options.html, which iterates over a list ofAcquisitionobjects and renders download links generically.Acquisition model update
labelfield to theAcquisitiondataclass.from_jsonandfrom_opds_jsonto populate this field, preserving existing UI text cleanly.Provider updates
Rendering refactor
AbstractBookProvider.render_download_optionsto use the unified template.render_download_optionsforBetterWorldBooksProviderto avoid purchase links appearing in the download options section.Cleanup
🔍 Behavior Notes
🧪 Testing
🙏 Reviewer Notes
Happy to adjust if there’s a preference for handling labels, filtering, or provider-specific affordances differently.