From d9f6449767bfc1f9fea9e8fc353d856bdd5d8ab8 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 6 Aug 2023 17:52:23 -0700 Subject: [PATCH 01/16] Pre-populate sort title in edit book form if not provided It's confusing to edit a book when this isn't set, so this provides the best-guess version of the sort title if there isn't one provided, and allows the user to change it as needed. --- bookwyrm/models/book.py | 17 ++++++++--------- bookwyrm/views/books/edit_book.py | 4 ++++ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index 8cb47e5c8..a53321b26 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -217,6 +217,13 @@ class Book(BookDataModel): """editions and works both use "book" instead of model_name""" return f"https://{DOMAIN}/book/{self.id}" + def guess_sort_title(self): + """Get a best-guess sort title for the current book""" + articles = chain( + *(LANGUAGE_ARTICLES.get(language, ()) for language in tuple(self.languages)) + ) + return re.sub(f'^{" |^".join(articles)} ', "", str(self.title).lower()) + def __repr__(self): # pylint: disable=consider-using-f-string return "<{} key={!r} title={!r}>".format( @@ -375,15 +382,7 @@ class Edition(Book): # Create sort title by removing articles from title if self.sort_title in [None, ""]: if self.sort_title in [None, ""]: - articles = chain( - *( - LANGUAGE_ARTICLES.get(language, ()) - for language in tuple(self.languages) - ) - ) - self.sort_title = re.sub( - f'^{" |^".join(articles)} ', "", str(self.title).lower() - ) + self.sort_title = self.guess_sort_title() return super().save(*args, **kwargs) diff --git a/bookwyrm/views/books/edit_book.py b/bookwyrm/views/books/edit_book.py index 2a7f36dbb..ae492374f 100644 --- a/bookwyrm/views/books/edit_book.py +++ b/bookwyrm/views/books/edit_book.py @@ -32,6 +32,9 @@ class EditBook(View): def get(self, request, book_id): """info about a book""" book = get_edition(book_id) + # This doesn't update the sort title, just pre-populates it in the form + if book.sort_title in ["", None]: + book.sort_title = book.guess_sort_title() if not book.description: book.description = book.parent_work.description data = {"book": book, "form": forms.EditionForm(instance=book)} @@ -40,6 +43,7 @@ class EditBook(View): def post(self, request, book_id): """edit a book cool""" book = get_object_or_404(models.Edition, id=book_id) + form = forms.EditionForm(request.POST, request.FILES, instance=book) data = {"book": book, "form": form} From 1e0fe6d7c8b7fb58a02dec6b7f5070f8fd3b28e9 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 19 Aug 2023 15:06:57 -0700 Subject: [PATCH 02/16] Remove duplicate if statement --- bookwyrm/models/book.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index a53321b26..d0c3c7fd3 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -381,8 +381,7 @@ class Edition(Book): # Create sort title by removing articles from title if self.sort_title in [None, ""]: - if self.sort_title in [None, ""]: - self.sort_title = self.guess_sort_title() + self.sort_title = self.guess_sort_title() return super().save(*args, **kwargs) From 3760e3b45c0d1d81f07a501025f3d769f995b36a Mon Sep 17 00:00:00 2001 From: Joeri de Ruiter Date: Mon, 21 Aug 2023 15:46:24 +0200 Subject: [PATCH 03/16] Tests for ISBN hyphenation --- bookwyrm/tests/test_isbn.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 bookwyrm/tests/test_isbn.py diff --git a/bookwyrm/tests/test_isbn.py b/bookwyrm/tests/test_isbn.py new file mode 100644 index 000000000..b528e9210 --- /dev/null +++ b/bookwyrm/tests/test_isbn.py @@ -0,0 +1,31 @@ +""" test ISBN hyphenator for books """ +from django.test import TestCase + +from bookwyrm.isbn.isbn import hyphenator_singleton as hyphenator + + +class TestISBN(TestCase): + """isbn hyphenator""" + + def test_isbn_hyphenation(self): + """different isbn hyphenations""" + # nothing + self.assertEqual(hyphenator.hyphenate(None), None) + # 978-0 (English language) 3700000-6389999 + self.assertEqual(hyphenator.hyphenate("9780439554930"), "978-0-439-55493-0") + # 978-2 (French language) 0000000-1999999 + self.assertEqual(hyphenator.hyphenate("9782070100927"), "978-2-07-010092-7") + # 978-3 (German language) 2000000-6999999 + self.assertEqual(hyphenator.hyphenate("9783518188125"), "978-3-518-18812-5") + # 978-4 (Japan) 0000000-1999999 + self.assertEqual(hyphenator.hyphenate("9784101050454"), "978-4-10-105045-4") + # 978-626 (Taiwan) 9500000-9999999 + self.assertEqual(hyphenator.hyphenate("9786269533251"), "978-626-95332-5-1") + # 979-8 (United States) 4000000-8499999 + self.assertEqual(hyphenator.hyphenate("9798627974040"), "979-8-6279-7404-0") + # 978-626 (Taiwan) 8000000-9499999 (unassigned) + self.assertEqual(hyphenator.hyphenate("9786268533251"), "9786268533251") + # 978 range 6600000-6999999 (unassigned) + self.assertEqual(hyphenator.hyphenate("9786769533251"), "9786769533251") + # 979-8 (United States) 2300000-3499999 (unassigned) + self.assertEqual(hyphenator.hyphenate("9798311111111"), "9798311111111") From f6d87861792e67bbc4819adeaf40f024e8502354 Mon Sep 17 00:00:00 2001 From: Joeri de Ruiter Date: Mon, 21 Aug 2023 15:46:50 +0200 Subject: [PATCH 04/16] Type annotations for bookwyrm.isbn --- bookwyrm/isbn/isbn.py | 81 +++++++++++++++++++++++++++++++++---------- bookwyrm/settings.py | 4 ++- mypy.ini | 3 ++ 3 files changed, 69 insertions(+), 19 deletions(-) diff --git a/bookwyrm/isbn/isbn.py b/bookwyrm/isbn/isbn.py index e07d2100d..4cc7f47dd 100644 --- a/bookwyrm/isbn/isbn.py +++ b/bookwyrm/isbn/isbn.py @@ -1,11 +1,20 @@ """ Use the range message from isbn-international to hyphenate ISBNs """ import os +from typing import Optional from xml.etree import ElementTree +from xml.etree.ElementTree import Element + import requests from bookwyrm import settings +def _get_rules(element: Element) -> list[Element]: + if (rules_el := element.find("Rules")) is not None: + return rules_el.findall("Rule") + return [] + + class IsbnHyphenator: """Class to manage the range message xml file and use it to hyphenate ISBNs""" @@ -15,58 +24,94 @@ class IsbnHyphenator: ) __element_tree = None - def update_range_message(self): + def update_range_message(self) -> None: """Download the range message xml file and save it locally""" response = requests.get(self.__range_message_url) with open(self.__range_file_path, "w", encoding="utf-8") as file: file.write(response.text) self.__element_tree = None - def hyphenate(self, isbn_13): + def hyphenate(self, isbn_13: Optional[str]) -> Optional[str]: """hyphenate the given ISBN-13 number using the range message""" if isbn_13 is None: return None + if self.__element_tree is None: self.__element_tree = ElementTree.parse(self.__range_file_path) + gs1_prefix = isbn_13[:3] reg_group = self.__find_reg_group(isbn_13, gs1_prefix) if reg_group is None: return isbn_13 # failed to hyphenate + registrant = self.__find_registrant(isbn_13, gs1_prefix, reg_group) if registrant is None: return isbn_13 # failed to hyphenate + publication = isbn_13[len(gs1_prefix) + len(reg_group) + len(registrant) : -1] check_digit = isbn_13[-1:] return "-".join((gs1_prefix, reg_group, registrant, publication, check_digit)) - def __find_reg_group(self, isbn_13, gs1_prefix): - for ean_ucc_el in self.__element_tree.find("EAN.UCCPrefixes").findall( - "EAN.UCC" - ): - if ean_ucc_el.find("Prefix").text == gs1_prefix: - for rule_el in ean_ucc_el.find("Rules").findall("Rule"): - length = int(rule_el.find("Length").text) + def __find_reg_group(self, isbn_13: str, gs1_prefix: str) -> Optional[str]: + if self.__element_tree is None: + self.__element_tree = ElementTree.parse(self.__range_file_path) + + ucc_prefixes_el = self.__element_tree.find("EAN.UCCPrefixes") + if ucc_prefixes_el is None: + return None + + for ean_ucc_el in ucc_prefixes_el.findall("EAN.UCC"): + if ( + prefix_el := ean_ucc_el.find("Prefix") + ) is not None and prefix_el.text == gs1_prefix: + for rule_el in _get_rules(ean_ucc_el): + length_el = rule_el.find("Length") + if length_el is None: + continue + length = int(text) if (text := length_el.text) else 0 if length == 0: continue - reg_grp_range = [ - int(x[:length]) for x in rule_el.find("Range").text.split("-") - ] + + range_el = rule_el.find("Range") + if range_el is None or range_el.text is None: + continue + + reg_grp_range = [int(x[:length]) for x in range_el.text.split("-")] reg_group = isbn_13[len(gs1_prefix) : len(gs1_prefix) + length] if reg_grp_range[0] <= int(reg_group) <= reg_grp_range[1]: return reg_group return None return None - def __find_registrant(self, isbn_13, gs1_prefix, reg_group): + def __find_registrant( + self, isbn_13: str, gs1_prefix: str, reg_group: str + ) -> Optional[str]: from_ind = len(gs1_prefix) + len(reg_group) - for group_el in self.__element_tree.find("RegistrationGroups").findall("Group"): - if group_el.find("Prefix").text == "-".join((gs1_prefix, reg_group)): - for rule_el in group_el.find("Rules").findall("Rule"): - length = int(rule_el.find("Length").text) + + if self.__element_tree is None: + self.__element_tree = ElementTree.parse(self.__range_file_path) + + reg_groups_el = self.__element_tree.find("RegistrationGroups") + if reg_groups_el is None: + return None + + for group_el in reg_groups_el.findall("Group"): + if ( + prefix_el := group_el.find("Prefix") + ) is not None and prefix_el.text == "-".join((gs1_prefix, reg_group)): + for rule_el in _get_rules(group_el): + length_el = rule_el.find("Length") + if length_el is None: + continue + length = int(text) if (text := length_el.text) else 0 if length == 0: continue + + range_el = rule_el.find("Range") + if range_el is None or range_el.text is None: + continue registrant_range = [ - int(x[:length]) for x in rule_el.find("Range").text.split("-") + int(x[:length]) for x in range_el.text.split("-") ] registrant = isbn_13[from_ind : from_ind + length] if registrant_range[0] <= int(registrant) <= registrant_range[1]: diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index 829ddaef7..751ab5687 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -1,5 +1,7 @@ """ bookwyrm settings and configuration """ import os +from typing import AnyStr + from environs import Env import requests @@ -37,7 +39,7 @@ EMAIL_SENDER_DOMAIN = env("EMAIL_SENDER_DOMAIN", DOMAIN) EMAIL_SENDER = f"{EMAIL_SENDER_NAME}@{EMAIL_SENDER_DOMAIN}" # Build paths inside the project like this: os.path.join(BASE_DIR, ...) -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +BASE_DIR: AnyStr = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) LOCALE_PATHS = [ os.path.join(BASE_DIR, "locale"), ] diff --git a/mypy.ini b/mypy.ini index 2a29e314f..27891f501 100644 --- a/mypy.ini +++ b/mypy.ini @@ -13,6 +13,9 @@ implicit_reexport = True [mypy-bookwyrm.connectors.*] ignore_errors = False +[mypy-bookwyrm.isbn.*] +ignore_errors = False + [mypy-celerywyrm.*] ignore_errors = False From 1c9da7b84b31b7e2403242fee9332a6be31bbee4 Mon Sep 17 00:00:00 2001 From: 0x29a Date: Fri, 25 Aug 2023 14:11:29 +0200 Subject: [PATCH 05/16] chore: bump version to match the latest tag --- bookwyrm/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index 829ddaef7..5c562ba26 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -12,7 +12,7 @@ from django.core.exceptions import ImproperlyConfigured env = Env() env.read_env() DOMAIN = env("DOMAIN") -VERSION = "0.6.4" +VERSION = "0.6.5" RELEASE_API = env( "RELEASE_API", From d560a6baeff5e61cd11d19588a72cd180f147c9b Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Wed, 30 Aug 2023 20:15:20 +1000 Subject: [PATCH 06/16] fix opensearch template * "method" is not a valid attribute of the `Url` element * "ShortName" cannot be empty - fixed site_name being used before it was assigned --- bookwyrm/templates/opensearch.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bookwyrm/templates/opensearch.xml b/bookwyrm/templates/opensearch.xml index 3d5f124b3..fd5c8f231 100644 --- a/bookwyrm/templates/opensearch.xml +++ b/bookwyrm/templates/opensearch.xml @@ -3,14 +3,13 @@ xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/" > - {{ site_name }} + {{ site.name }} {% blocktrans trimmed with site_name=site.name %} {{ site_name }} search {% endblocktrans %} {{ image }} From b0601a0958d48d098e9ee07823d6b797cde44fe0 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Fri, 1 Sep 2023 16:59:56 -0700 Subject: [PATCH 07/16] Makes deleting announcements only work via POST --- bookwyrm/views/admin/announcements.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bookwyrm/views/admin/announcements.py b/bookwyrm/views/admin/announcements.py index 0b5ce9fa4..c5a7c80ff 100644 --- a/bookwyrm/views/admin/announcements.py +++ b/bookwyrm/views/admin/announcements.py @@ -5,6 +5,7 @@ from django.shortcuts import get_object_or_404, redirect from django.template.response import TemplateResponse from django.utils.decorators import method_decorator from django.views import View +from django.views.decorators.http import require_POST from bookwyrm import forms, models from bookwyrm.settings import PAGE_LENGTH @@ -108,6 +109,7 @@ class EditAnnouncement(View): @login_required @permission_required("bookwyrm.edit_instance_settings", raise_exception=True) +@require_POST def delete_announcement(_, announcement_id): """delete announcement""" announcement = get_object_or_404(models.Announcement, id=announcement_id) From 2260e14868c16d45c36324ae75a71e0bd3b1498d Mon Sep 17 00:00:00 2001 From: JJimenez71 Date: Thu, 7 Sep 2023 19:30:29 -0600 Subject: [PATCH 08/16] Pinned versions of docker containers --- docker-compose.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 800690206..c327c10a9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: '3' services: nginx: - image: nginx:latest + image: nginx:1.25.2 restart: unless-stopped ports: - "1333:80" @@ -38,7 +38,7 @@ services: ports: - "8000" redis_activity: - image: redis + image: redis:7.2.1 command: redis-server --requirepass ${REDIS_ACTIVITY_PASSWORD} --appendonly yes --port ${REDIS_ACTIVITY_PORT} volumes: - ./redis.conf:/etc/redis/redis.conf @@ -48,7 +48,7 @@ services: - main restart: on-failure redis_broker: - image: redis + image: redis:7.2.1 command: redis-server --requirepass ${REDIS_BROKER_PASSWORD} --appendonly yes --port ${REDIS_BROKER_PORT} volumes: - ./redis.conf:/etc/redis/redis.conf From d8ba1f430928d57a13c0936fd8939387771af90b Mon Sep 17 00:00:00 2001 From: FoW Date: Fri, 8 Sep 2023 22:52:11 +0900 Subject: [PATCH 09/16] Correct EPUB spelling --- bookwyrm/static/js/autocomplete.js | 2 +- bookwyrm/templates/book/file_links/add_link_modal.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/static/js/autocomplete.js b/bookwyrm/static/js/autocomplete.js index 84474e43c..a98cd9634 100644 --- a/bookwyrm/static/js/autocomplete.js +++ b/bookwyrm/static/js/autocomplete.js @@ -106,7 +106,7 @@ const tries = { e: { p: { u: { - b: "ePub", + b: "EPUB", }, }, }, diff --git a/bookwyrm/templates/book/file_links/add_link_modal.html b/bookwyrm/templates/book/file_links/add_link_modal.html index 67b437bd7..8ed4389ff 100644 --- a/bookwyrm/templates/book/file_links/add_link_modal.html +++ b/bookwyrm/templates/book/file_links/add_link_modal.html @@ -35,7 +35,7 @@ required="" id="id_filetype" value="{% firstof file_link_form.filetype.value '' %}" - placeholder="ePub" + placeholder="EPUB" list="mimetypes-list" data-autocomplete="mimetype" > From 05f8bd0d3c500e91585253ad9e5c393a11aba58b Mon Sep 17 00:00:00 2001 From: Joeri de Ruiter Date: Wed, 13 Sep 2023 09:46:31 +0200 Subject: [PATCH 10/16] parent_work was not always included in work when needed --- bookwyrm/templates/book/edit/edit_book_form.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/templates/book/edit/edit_book_form.html b/bookwyrm/templates/book/edit/edit_book_form.html index 23cc6d097..4cc3965e7 100644 --- a/bookwyrm/templates/book/edit/edit_book_form.html +++ b/bookwyrm/templates/book/edit/edit_book_form.html @@ -10,7 +10,7 @@ {% csrf_token %} -{% if form.parent_work %} +{% if book.parent_work.id or form.parent_work %} {% endif %} From 25fd7276ea23d69a707414eb874363601cdcc433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adeodato=20Sim=C3=B3?= Date: Sun, 17 Sep 2023 10:46:11 -0300 Subject: [PATCH 11/16] `pure_content()` refactor: shorter conditionals --- bookwyrm/models/status.py | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index e51f2ba07..8c98028a0 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -320,17 +320,14 @@ class Comment(BookStatus): @property def pure_content(self): """indicate the book in question for mastodon (or w/e) users""" - if self.progress_mode == "PG" and self.progress and (self.progress > 0): - return_value = ( - f'{self.content}

(comment on ' - f'"{self.book.title}", page {self.progress})

' - ) - else: - return_value = ( - f'{self.content}

(comment on ' - f'"{self.book.title}")

' - ) - return return_value + progress = self.progress or 0 + citation = ( + f'comment on ' + f'"{self.book.title}"' + ) + if self.progress_mode == "PG" and progress > 0: + citation += f", page {progress}" + return f"{self.content}

({citation})

" activity_serializer = activitypub.Comment @@ -359,17 +356,10 @@ class Quotation(BookStatus): """indicate the book in question for mastodon (or w/e) users""" quote = re.sub(r"^

", '

"', self.quote) quote = re.sub(r"

$", '"

', quote) + citation = f'-- "{self.book.title}"' if self.position_mode == "PG" and self.position and (self.position > 0): - return_value = ( - f'{quote}

-- ' - f'"{self.book.title}", page {self.position}

{self.content}' - ) - else: - return_value = ( - f'{quote}

-- ' - f'"{self.book.title}"

{self.content}' - ) - return return_value + citation += f", page {self.position}" + return f"{quote}

{citation}

{self.content}" activity_serializer = activitypub.Quotation From 1322a0c6939f364a023aa0df1c01e9685d4c31f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adeodato=20Sim=C3=B3?= Date: Sun, 17 Sep 2023 15:05:34 -0300 Subject: [PATCH 12/16] =?UTF-8?q?Substitute=20=E2=80=9Cp.=E2=80=9D=20for?= =?UTF-8?q?=20=E2=80=9Cpage=E2=80=9D=20in=20page=20progress=20serializatio?= =?UTF-8?q?n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bookwyrm/models/status.py | 4 ++-- bookwyrm/tests/models/test_status_model.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 8c98028a0..1040ace8f 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -326,7 +326,7 @@ class Comment(BookStatus): f'"{self.book.title}"' ) if self.progress_mode == "PG" and progress > 0: - citation += f", page {progress}" + citation += f", p. {progress}" return f"{self.content}

({citation})

" activity_serializer = activitypub.Comment @@ -358,7 +358,7 @@ class Quotation(BookStatus): quote = re.sub(r"

$", '"

', quote) citation = f'-- "{self.book.title}"' if self.position_mode == "PG" and self.position and (self.position > 0): - citation += f", page {self.position}" + citation += f", p. {self.position}" return f"{quote}

{citation}

{self.content}" activity_serializer = activitypub.Quotation diff --git a/bookwyrm/tests/models/test_status_model.py b/bookwyrm/tests/models/test_status_model.py index 72aa0ca6c..d41b80575 100644 --- a/bookwyrm/tests/models/test_status_model.py +++ b/bookwyrm/tests/models/test_status_model.py @@ -249,14 +249,14 @@ class Status(TestCase): def test_comment_to_pure_activity(self, *_): """subclass of the base model version with a "pure" serializer""" status = models.Comment.objects.create( - content="test content", user=self.local_user, book=self.book + content="test content", user=self.local_user, book=self.book, progress=27 ) activity = status.to_activity(pure=True) self.assertEqual(activity["id"], status.remote_id) self.assertEqual(activity["type"], "Note") self.assertEqual( activity["content"], - f'test content

(comment on "Test Edition")

', + f'test content

(comment on "Test Edition", p. 27)

', ) self.assertEqual(activity["attachment"][0]["type"], "Document") # self.assertTrue( From ce3885d4f6bed6761cac6761682a8982a4170188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adeodato=20Sim=C3=B3?= Date: Sun, 17 Sep 2023 01:40:23 -0300 Subject: [PATCH 13/16] Use `endposition` when serializing Quotation --- bookwyrm/models/status.py | 13 ++++++++++-- bookwyrm/tests/models/test_status_model.py | 23 ++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 1040ace8f..d51de5278 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -1,5 +1,6 @@ """ models for storing different kinds of Activities """ from dataclasses import MISSING +from typing import Optional import re from django.apps import apps @@ -351,14 +352,22 @@ class Quotation(BookStatus): blank=True, ) + def _format_position(self) -> Optional[str]: + """serialize page position""" + beg = self.position + end = self.endposition or 0 + if self.position_mode != "PG" or not beg: + return None + return f"pp. {beg}-{end}" if end > beg else f"p. {beg}" + @property def pure_content(self): """indicate the book in question for mastodon (or w/e) users""" quote = re.sub(r"^

", '

"', self.quote) quote = re.sub(r"

$", '"

', quote) citation = f'-- "{self.book.title}"' - if self.position_mode == "PG" and self.position and (self.position > 0): - citation += f", p. {self.position}" + if position := self._format_position(): + citation += f", {position}" return f"{quote}

{citation}

{self.content}" activity_serializer = activitypub.Quotation diff --git a/bookwyrm/tests/models/test_status_model.py b/bookwyrm/tests/models/test_status_model.py index d41b80575..15d73de9c 100644 --- a/bookwyrm/tests/models/test_status_model.py +++ b/bookwyrm/tests/models/test_status_model.py @@ -306,6 +306,29 @@ class Status(TestCase): ) self.assertEqual(activity["attachment"][0]["name"], "Test Edition") + def test_quotation_page_serialization(self, *_): + """serialization of quotation page position""" + tests = [ + ("single pos", 7, None, "p. 7"), + ("page range", 7, 10, "pp. 7-10"), + ] + for desc, beg, end, pages in tests: + with self.subTest(desc): + status = models.Quotation.objects.create( + quote="

my quote

", + content="", + user=self.local_user, + book=self.book, + position=beg, + endposition=end, + position_mode="PG", + ) + activity = status.to_activity(pure=True) + self.assertRegex( + activity["content"], + f'^

"my quote"

-- , {pages}

$', + ) + def test_review_to_activity(self, *_): """subclass of the base model version with a "pure" serializer""" status = models.Review.objects.create( From 1e495684af34a9377373e19a86c4dbaccfd3be2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adeodato=20Sim=C3=B3?= Date: Mon, 18 Sep 2023 18:57:52 -0300 Subject: [PATCH 14/16] Serve static files in debug mode --- bookwyrm/urls.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 0ebd7925c..05972ee73 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -1,6 +1,7 @@ """ url routing for the app and api """ from django.conf.urls.static import static from django.contrib import admin +from django.contrib.staticfiles.urls import staticfiles_urlpatterns from django.urls import path, re_path from django.views.generic.base import TemplateView @@ -774,5 +775,8 @@ urlpatterns = [ path("guided-tour/", views.toggle_guided_tour), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) +# Serves /static when DEBUG is true. +urlpatterns.extend(staticfiles_urlpatterns()) + # pylint: disable=invalid-name handler500 = "bookwyrm.views.server_error" From cc05cabcb57d340f87a3ae44856fe6574bec7eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adeodato=20Sim=C3=B3?= Date: Sun, 17 Sep 2023 15:32:29 -0300 Subject: [PATCH 15/16] Note content: use italics for book titles + em-dash for Quotation --- bookwyrm/models/status.py | 7 ++++--- bookwyrm/tests/models/test_status_model.py | 18 +++++++++++++----- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index d51de5278..5d6109468 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -270,7 +270,7 @@ class GeneratedNote(Status): """indicate the book in question for mastodon (or w/e) users""" message = self.content books = ", ".join( - f'"{book.title}"' + f'{book.title}' for book in self.mention_books.all() ) return f"{self.user.display_name} {message} {books}" @@ -324,7 +324,7 @@ class Comment(BookStatus): progress = self.progress or 0 citation = ( f'comment on ' - f'"{self.book.title}"' + f"{self.book.title}" ) if self.progress_mode == "PG" and progress > 0: citation += f", p. {progress}" @@ -365,7 +365,8 @@ class Quotation(BookStatus): """indicate the book in question for mastodon (or w/e) users""" quote = re.sub(r"^

", '

"', self.quote) quote = re.sub(r"

$", '"

', quote) - citation = f'-- "{self.book.title}"' + title, href = self.book.title, self.book.remote_id + citation = f'— {title}' if position := self._format_position(): citation += f", {position}" return f"{quote}

{citation}

{self.content}" diff --git a/bookwyrm/tests/models/test_status_model.py b/bookwyrm/tests/models/test_status_model.py index 15d73de9c..760849f28 100644 --- a/bookwyrm/tests/models/test_status_model.py +++ b/bookwyrm/tests/models/test_status_model.py @@ -212,7 +212,7 @@ class Status(TestCase): def test_generated_note_to_pure_activity(self, *_): """subclass of the base model version with a "pure" serializer""" status = models.GeneratedNote.objects.create( - content="test content", user=self.local_user + content="reads", user=self.local_user ) status.mention_books.set([self.book]) status.mention_users.set([self.local_user]) @@ -220,7 +220,7 @@ class Status(TestCase): self.assertEqual(activity["id"], status.remote_id) self.assertEqual( activity["content"], - f'mouse test content "Test Edition"', + f'mouse reads Test Edition', ) self.assertEqual(len(activity["tag"]), 2) self.assertEqual(activity["type"], "Note") @@ -256,7 +256,11 @@ class Status(TestCase): self.assertEqual(activity["type"], "Note") self.assertEqual( activity["content"], - f'test content

(comment on "Test Edition", p. 27)

', + ( + "test content" + f'

(comment on ' + "Test Edition, p. 27)

" + ), ) self.assertEqual(activity["attachment"][0]["type"], "Document") # self.assertTrue( @@ -295,7 +299,11 @@ class Status(TestCase): self.assertEqual(activity["type"], "Note") self.assertEqual( activity["content"], - f'a sickening sense

-- "Test Edition"

test content', + ( + "a sickening sense " + f'

' + "Test Edition

test content" + ), ) self.assertEqual(activity["attachment"][0]["type"], "Document") self.assertTrue( @@ -326,7 +334,7 @@ class Status(TestCase): activity = status.to_activity(pure=True) self.assertRegex( activity["content"], - f'^

"my quote"

-- , {pages}

$', + f'^

"my quote"

, {pages}

$', ) def test_review_to_activity(self, *_): From fadf30b94216a407aceb3d089595fc40e8bc446e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adeodato=20Sim=C3=B3?= Date: Sun, 17 Sep 2023 15:45:27 -0300 Subject: [PATCH 16/16] Also use italics for book title in editions.html template --- bookwyrm/templates/book/editions/editions.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/templates/book/editions/editions.html b/bookwyrm/templates/book/editions/editions.html index aa2b68bdb..e1766d1e1 100644 --- a/bookwyrm/templates/book/editions/editions.html +++ b/bookwyrm/templates/book/editions/editions.html @@ -5,7 +5,7 @@ {% block content %}
-

{% blocktrans with work_path=work.local_path work_title=work|book_title %}Editions of "{{ work_title }}"{% endblocktrans %}

+

{% blocktrans with work_path=work.local_path work_title=work|book_title %}Editions of {{ work_title }}{% endblocktrans %}

{% include 'book/editions/edition_filters.html' %}