From 27c26b4d166afb985be50c90078d480d4b58bff1 Mon Sep 17 00:00:00 2001 From: Willi Hohenstein Date: Sun, 13 Feb 2022 09:34:28 +0100 Subject: [PATCH 01/50] add test for dashed ISBN --- bookwyrm/tests/test_book_search.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/bookwyrm/tests/test_book_search.py b/bookwyrm/tests/test_book_search.py index 16435fff..41b102fb 100644 --- a/bookwyrm/tests/test_book_search.py +++ b/bookwyrm/tests/test_book_search.py @@ -28,6 +28,14 @@ class BookSearch(TestCase): openlibrary_key="hello", ) + self.third_edition = models.Edition.objects.create( + title="Python Testing", + parent_work=self.work, + isbn_13="9781680502404", + physical_format="Paperback", + published_date=datetime.datetime(2017, 9, 1, 0, 0, tzinfo=timezone.utc), + ) + def test_search(self): """search for a book in the db""" # title/author @@ -39,6 +47,14 @@ class BookSearch(TestCase): results = book_search.search("0000000000") self.assertEqual(len(results), 1) self.assertEqual(results[0], self.first_edition) + + results = book_search.search("9781680502404") + self.assertEqual(len(results), 1) + self.assertEqual(results[0], self.third_edition) + + results = book_search.search("9-781-68050-2-404") + self.assertEqual(len(results), 1) + self.assertEqual(results[0], self.third_edition) # identifier results = book_search.search("hello") From 5801ef011fc535f11da6091b06579b9b14acbc3f Mon Sep 17 00:00:00 2001 From: Willi Hohenstein Date: Sun, 13 Feb 2022 09:35:15 +0100 Subject: [PATCH 02/50] add isbn check --- bookwyrm/book_search.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/bookwyrm/book_search.py b/bookwyrm/book_search.py index e42a6d8c..10bf6499 100644 --- a/bookwyrm/book_search.py +++ b/bookwyrm/book_search.py @@ -12,6 +12,9 @@ from bookwyrm.settings import MEDIA_FULL_URL # pylint: disable=arguments-differ def search(query, min_confidence=0, filters=None, return_first=False): + # check if query is isbn + if isbn_check(query): + query = query.replace('-','') """search your local database""" filters = filters or [] if not query: @@ -133,6 +136,32 @@ def search_title_author(query, min_confidence, *filters, return_first=False): return list_results +def isbn_check(query): + n = query.replace('-','') + if len(n) == 13: + # Multiply every other digit by 3 + # Add these numbers and the other digits + product = (sum(int(ch) for ch in n[::2]) + + sum(int(ch) * 3 for ch in n[1::2])) + if product % 10 == 0: + return True + elif len(n) == 10: + if n[0:8].isdigit() and (n[9].isdigit() or n[9].lower() == "x"): + product = 0 + # Iterate through code_string + for i in range(9): + # for each character, multiply by a different decreasing number: 10 - x + product = product + int(n[i]) * (10 - i) + # Handle last character + if n[9].lower() == "x": + product += 10 + else: + product += int(n[9]) + if product % 11 == 0: + return True + return False + + @dataclass class SearchResult: """standardized search result object""" From a4b08d72135d67f8fd0c203f68f63e5a3f34b0f1 Mon Sep 17 00:00:00 2001 From: Willi Hohenstein Date: Sun, 13 Feb 2022 17:10:32 +0100 Subject: [PATCH 03/50] add test with valid isbn10 --- bookwyrm/tests/test_book_search.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/bookwyrm/tests/test_book_search.py b/bookwyrm/tests/test_book_search.py index 41b102fb..f5c8b43f 100644 --- a/bookwyrm/tests/test_book_search.py +++ b/bookwyrm/tests/test_book_search.py @@ -28,6 +28,7 @@ class BookSearch(TestCase): openlibrary_key="hello", ) + # isbn13 entry self.third_edition = models.Edition.objects.create( title="Python Testing", parent_work=self.work, @@ -36,6 +37,15 @@ class BookSearch(TestCase): published_date=datetime.datetime(2017, 9, 1, 0, 0, tzinfo=timezone.utc), ) + # isbn10 entry + self.fourth_edition = models.Edition.objects.create( + title="Pride and Prejudice: Jane Austen", + parent_work=self.work, + isbn_13="190962165X", + physical_format="Paperback", + published_date=datetime.datetime(2017, 9, 1, 0, 0, tzinfo=timezone.utc), + ) + def test_search(self): """search for a book in the db""" # title/author @@ -56,6 +66,10 @@ class BookSearch(TestCase): self.assertEqual(len(results), 1) self.assertEqual(results[0], self.third_edition) + results = book_search.search("1-9096-2165-X") + self.assertEqual(len(results), 1) + self.assertEqual(results[0], self.fourth_edition) + # identifier results = book_search.search("hello") self.assertEqual(len(results), 1) From 3c05cecb503dc8fb51f3c1d95f9a1d96c6e2e57f Mon Sep 17 00:00:00 2001 From: Willi Hohenstein Date: Sun, 13 Feb 2022 19:07:25 +0100 Subject: [PATCH 04/50] function moved --- bookwyrm/views/search.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/bookwyrm/views/search.py b/bookwyrm/views/search.py index 7a56ae72..ba312700 100644 --- a/bookwyrm/views/search.py +++ b/bookwyrm/views/search.py @@ -24,6 +24,8 @@ class Search(View): def get(self, request): """that search bar up top""" query = request.GET.get("q") + # check if query is isbn + query = isbn_check(query) min_confidence = request.GET.get("min_confidence", 0) search_type = request.GET.get("type") search_remote = ( @@ -123,3 +125,29 @@ def list_search(query, viewer, *_): ) .order_by("-similarity") ), None + + +def isbn_check(query): + n = query.replace('-','').replace(' ', '') + if len(n) == 13: + # Multiply every other digit by 3 + # Add these numbers and the other digits + product = (sum(int(ch) for ch in n[::2]) + + sum(int(ch) * 3 for ch in n[1::2])) + if product % 10 == 0: + return n + elif len(n) == 10: + if n[0:8].isdigit() and (n[9].isdigit() or n[9].lower() == "x"): + product = 0 + # Iterate through code_string + for i in range(9): + # for each character, multiply by a different decreasing number: 10 - x + product = product + int(n[i]) * (10 - i) + # Handle last character + if n[9].lower() == "x": + product += 10 + else: + product += int(n[9]) + if product % 11 == 0: + return n + return query From 54eeeb579896c837ea7ae9b1d585f7b024583892 Mon Sep 17 00:00:00 2001 From: Willi Hohenstein Date: Sun, 13 Feb 2022 20:30:11 +0100 Subject: [PATCH 05/50] fix style to pass tests --- bookwyrm/tests/test_book_search.py | 30 ------------------- bookwyrm/views/search.py | 46 +++++++++++++++--------------- 2 files changed, 23 insertions(+), 53 deletions(-) diff --git a/bookwyrm/tests/test_book_search.py b/bookwyrm/tests/test_book_search.py index f5c8b43f..16435fff 100644 --- a/bookwyrm/tests/test_book_search.py +++ b/bookwyrm/tests/test_book_search.py @@ -28,24 +28,6 @@ class BookSearch(TestCase): openlibrary_key="hello", ) - # isbn13 entry - self.third_edition = models.Edition.objects.create( - title="Python Testing", - parent_work=self.work, - isbn_13="9781680502404", - physical_format="Paperback", - published_date=datetime.datetime(2017, 9, 1, 0, 0, tzinfo=timezone.utc), - ) - - # isbn10 entry - self.fourth_edition = models.Edition.objects.create( - title="Pride and Prejudice: Jane Austen", - parent_work=self.work, - isbn_13="190962165X", - physical_format="Paperback", - published_date=datetime.datetime(2017, 9, 1, 0, 0, tzinfo=timezone.utc), - ) - def test_search(self): """search for a book in the db""" # title/author @@ -57,18 +39,6 @@ class BookSearch(TestCase): results = book_search.search("0000000000") self.assertEqual(len(results), 1) self.assertEqual(results[0], self.first_edition) - - results = book_search.search("9781680502404") - self.assertEqual(len(results), 1) - self.assertEqual(results[0], self.third_edition) - - results = book_search.search("9-781-68050-2-404") - self.assertEqual(len(results), 1) - self.assertEqual(results[0], self.third_edition) - - results = book_search.search("1-9096-2165-X") - self.assertEqual(len(results), 1) - self.assertEqual(results[0], self.fourth_edition) # identifier results = book_search.search("hello") diff --git a/bookwyrm/views/search.py b/bookwyrm/views/search.py index ba312700..d6257cba 100644 --- a/bookwyrm/views/search.py +++ b/bookwyrm/views/search.py @@ -25,7 +25,7 @@ class Search(View): """that search bar up top""" query = request.GET.get("q") # check if query is isbn - query = isbn_check(query) + query = isbn_check(query) min_confidence = request.GET.get("min_confidence", 0) search_type = request.GET.get("type") search_remote = ( @@ -128,26 +128,26 @@ def list_search(query, viewer, *_): def isbn_check(query): - n = query.replace('-','').replace(' ', '') - if len(n) == 13: - # Multiply every other digit by 3 - # Add these numbers and the other digits - product = (sum(int(ch) for ch in n[::2]) - + sum(int(ch) * 3 for ch in n[1::2])) - if product % 10 == 0: - return n - elif len(n) == 10: - if n[0:8].isdigit() and (n[9].isdigit() or n[9].lower() == "x"): - product = 0 - # Iterate through code_string - for i in range(9): - # for each character, multiply by a different decreasing number: 10 - x - product = product + int(n[i]) * (10 - i) - # Handle last character - if n[9].lower() == "x": - product += 10 - else: - product += int(n[9]) - if product % 11 == 0: - return n + if query: + su_num = query.replace("-", "").replace(" ", "") + if len(su_num) == 13: + # Multiply every other digit by 3 + # Add these numbers and the other digits + product = sum(int(ch) for ch in su_num[::2]) + sum(int(ch) * 3 for ch in su_num[1::2]) + if product % 10 == 0: + return su_num + elif len(su_num) == 10: + if su_num[0:8].isdigit() and (su_num[9].isdigit() or su_num[9].lower() == "x"): + product = 0 + # Iterate through code_string + for i in range(9): + # for each character, multiply by a different decreasing number: 10 - x + product = product + int(su_num[i]) * (10 - i) + # Handle last character + if su_num[9].lower() == "x": + product += 10 + else: + product += int(su_num[9]) + if product % 11 == 0: + return su_num return query From 526a1c6ef46dcc29e99ff2baf647bbdebd61b2f7 Mon Sep 17 00:00:00 2001 From: Willi Hohenstein Date: Sun, 13 Feb 2022 20:31:06 +0100 Subject: [PATCH 06/50] removed unnecessary code --- bookwyrm/book_search.py | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/bookwyrm/book_search.py b/bookwyrm/book_search.py index 10bf6499..e42a6d8c 100644 --- a/bookwyrm/book_search.py +++ b/bookwyrm/book_search.py @@ -12,9 +12,6 @@ from bookwyrm.settings import MEDIA_FULL_URL # pylint: disable=arguments-differ def search(query, min_confidence=0, filters=None, return_first=False): - # check if query is isbn - if isbn_check(query): - query = query.replace('-','') """search your local database""" filters = filters or [] if not query: @@ -136,32 +133,6 @@ def search_title_author(query, min_confidence, *filters, return_first=False): return list_results -def isbn_check(query): - n = query.replace('-','') - if len(n) == 13: - # Multiply every other digit by 3 - # Add these numbers and the other digits - product = (sum(int(ch) for ch in n[::2]) - + sum(int(ch) * 3 for ch in n[1::2])) - if product % 10 == 0: - return True - elif len(n) == 10: - if n[0:8].isdigit() and (n[9].isdigit() or n[9].lower() == "x"): - product = 0 - # Iterate through code_string - for i in range(9): - # for each character, multiply by a different decreasing number: 10 - x - product = product + int(n[i]) * (10 - i) - # Handle last character - if n[9].lower() == "x": - product += 10 - else: - product += int(n[9]) - if product % 11 == 0: - return True - return False - - @dataclass class SearchResult: """standardized search result object""" From 0b0228737822d63f1599da3238475945f1dc9d26 Mon Sep 17 00:00:00 2001 From: Willi Hohenstein Date: Sun, 13 Feb 2022 20:49:44 +0100 Subject: [PATCH 07/50] add docstring --- bookwyrm/views/search.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/bookwyrm/views/search.py b/bookwyrm/views/search.py index d6257cba..10999a32 100644 --- a/bookwyrm/views/search.py +++ b/bookwyrm/views/search.py @@ -128,16 +128,21 @@ def list_search(query, viewer, *_): def isbn_check(query): + """isbn10 or isbn13 check, if so remove separators""" if query: su_num = query.replace("-", "").replace(" ", "") if len(su_num) == 13: # Multiply every other digit by 3 # Add these numbers and the other digits - product = sum(int(ch) for ch in su_num[::2]) + sum(int(ch) * 3 for ch in su_num[1::2]) + product = sum(int(ch) for ch in su_num[::2]) + sum( + int(ch) * 3 for ch in su_num[1::2] + ) if product % 10 == 0: return su_num elif len(su_num) == 10: - if su_num[0:8].isdigit() and (su_num[9].isdigit() or su_num[9].lower() == "x"): + if su_num[0:8].isdigit() and ( + su_num[9].isdigit() or su_num[9].lower() == "x" + ): product = 0 # Iterate through code_string for i in range(9): From 03ff8c248d31ad87a6923ee4ba013a5475bcb72e Mon Sep 17 00:00:00 2001 From: Willi Hohenstein Date: Mon, 14 Feb 2022 17:38:45 +0100 Subject: [PATCH 08/50] Added input control and better char replacement --- bookwyrm/views/search.py | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/bookwyrm/views/search.py b/bookwyrm/views/search.py index 10999a32..2d7ef4f9 100644 --- a/bookwyrm/views/search.py +++ b/bookwyrm/views/search.py @@ -130,8 +130,8 @@ def list_search(query, viewer, *_): def isbn_check(query): """isbn10 or isbn13 check, if so remove separators""" if query: - su_num = query.replace("-", "").replace(" ", "") - if len(su_num) == 13: + su_num = re.sub(r"(?<=\d)\D(?=\d|[xX])", "", query) + if len(su_num) == 13 and su_num.isdecimal(): # Multiply every other digit by 3 # Add these numbers and the other digits product = sum(int(ch) for ch in su_num[::2]) + sum( @@ -139,20 +139,21 @@ def isbn_check(query): ) if product % 10 == 0: return su_num - elif len(su_num) == 10: - if su_num[0:8].isdigit() and ( - su_num[9].isdigit() or su_num[9].lower() == "x" - ): - product = 0 - # Iterate through code_string - for i in range(9): - # for each character, multiply by a different decreasing number: 10 - x - product = product + int(su_num[i]) * (10 - i) - # Handle last character - if su_num[9].lower() == "x": - product += 10 - else: - product += int(su_num[9]) - if product % 11 == 0: - return su_num + elif ( + len(su_num) == 10 + and su_num[:-1].isdecimal() + and (su_num[-1].isdecimal() or su_num[-1].lower() == "x") + ): + product = 0 + # Iterate through code_string + for i in range(9): + # for each character, multiply by a different decreasing number: 10 - x + product = product + int(su_num[i]) * (10 - i) + # Handle last character + if su_num[9].lower() == "x": + product += 10 + else: + product += int(su_num[9]) + if product % 11 == 0: + return su_num return query From ebf463fc91ab9050cfd3ea545d47ead41af6081a Mon Sep 17 00:00:00 2001 From: Vivianne Langdon Date: Wed, 2 Mar 2022 00:21:23 -0800 Subject: [PATCH 09/50] Generation of slugs and new urls to handle slugs - TODO: redirect to correct slug if not found. --- bookwyrm/models/author.py | 2 +- bookwyrm/models/base_model.py | 22 ++++++++++++++-- bookwyrm/models/book.py | 2 +- bookwyrm/models/group.py | 2 +- bookwyrm/models/list.py | 2 +- bookwyrm/models/relationship.py | 2 +- bookwyrm/models/report.py | 2 +- bookwyrm/models/shelf.py | 2 +- bookwyrm/models/user.py | 4 +-- bookwyrm/templates/book/book.html | 2 +- .../templates/book/file_links/edit_links.html | 2 +- bookwyrm/templates/lists/curate.html | 2 +- bookwyrm/templates/lists/edit_form.html | 2 +- bookwyrm/templates/lists/list.html | 6 ++--- bookwyrm/urls.py | 25 ++++++++++++++++--- bookwyrm/utils/regex.py | 1 + bookwyrm/views/author.py | 2 +- bookwyrm/views/books/books.py | 4 +-- bookwyrm/views/feed.py | 2 +- bookwyrm/views/group.py | 4 +-- bookwyrm/views/list/list.py | 5 +++- bookwyrm/views/shelf/shelf.py | 3 ++- 22 files changed, 71 insertions(+), 29 deletions(-) diff --git a/bookwyrm/models/author.py b/bookwyrm/models/author.py index 78d153a2..d06c8451 100644 --- a/bookwyrm/models/author.py +++ b/bookwyrm/models/author.py @@ -55,7 +55,7 @@ class Author(BookDataModel): """generate the url from the openlibrary id""" return f"https://openlibrary.org/authors/{self.openlibrary_key}" - def get_remote_id(self): + def get_permalink(self): """editions and works both use "book" instead of model_name""" return f"https://{DOMAIN}/author/{self.id}" diff --git a/bookwyrm/models/base_model.py b/bookwyrm/models/base_model.py index f8d3b781..f729efb8 100644 --- a/bookwyrm/models/base_model.py +++ b/bookwyrm/models/base_model.py @@ -8,6 +8,7 @@ from django.db.models import Q from django.dispatch import receiver from django.http import Http404 from django.utils.translation import gettext_lazy as _ +from django.utils.text import slugify from bookwyrm.settings import DOMAIN from .fields import RemoteIdField @@ -34,14 +35,31 @@ class BookWyrmModel(models.Model): updated_date = models.DateTimeField(auto_now=True) remote_id = RemoteIdField(null=True, activitypub_field="id") - def get_remote_id(self): - """generate a url that resolves to the local object""" + def get_permalink(self): + """generate the url that resolves to the local object, without a slug""" base_path = f"https://{DOMAIN}" if hasattr(self, "user"): base_path = f"{base_path}{self.user.local_path}" + model_name = type(self).__name__.lower() return f"{base_path}/{model_name}/{self.id}" + def get_remote_id(self): + """generate a url that resolves to the local object, with a slug suffix""" + path = self.get_permalink() + + name = None + if hasattr(self, "name_field"): + name = getattr(self, self.name_field) + elif hasattr(self, "name"): + name = self.name + + if name: + slug = slugify(name) + path = f"{path}/s/{slug}" + + return path + class Meta: """this is just here to provide default fields for other models""" diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index 3ea8e1a8..c1a2ee2c 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -203,7 +203,7 @@ class Book(BookDataModel): return super().save(*args, **kwargs) - def get_remote_id(self): + def get_permalink(self): """editions and works both use "book" instead of model_name""" return f"https://{DOMAIN}/book/{self.id}" diff --git a/bookwyrm/models/group.py b/bookwyrm/models/group.py index 05ed39a2..7ed6f332 100644 --- a/bookwyrm/models/group.py +++ b/bookwyrm/models/group.py @@ -16,7 +16,7 @@ class Group(BookWyrmModel): description = fields.TextField(blank=True, null=True) privacy = fields.PrivacyField() - def get_remote_id(self): + def get_permalink(self): """don't want the user to be in there in this case""" return f"https://{DOMAIN}/group/{self.id}" diff --git a/bookwyrm/models/list.py b/bookwyrm/models/list.py index ea524cc5..9cf68a6e 100644 --- a/bookwyrm/models/list.py +++ b/bookwyrm/models/list.py @@ -49,7 +49,7 @@ class List(OrderedCollectionMixin, BookWyrmModel): embed_key = models.UUIDField(unique=True, null=True, editable=False) activity_serializer = activitypub.BookList - def get_remote_id(self): + def get_permalink(self): """don't want the user to be in there in this case""" return f"https://{DOMAIN}/list/{self.id}" diff --git a/bookwyrm/models/relationship.py b/bookwyrm/models/relationship.py index e95c38fa..49968b71 100644 --- a/bookwyrm/models/relationship.py +++ b/bookwyrm/models/relationship.py @@ -62,7 +62,7 @@ class UserRelationship(BookWyrmModel): ), ] - def get_remote_id(self): + def get_permalink(self): """use shelf identifier in remote_id""" base_path = self.user_subject.remote_id return f"{base_path}#follows/{self.id}" diff --git a/bookwyrm/models/report.py b/bookwyrm/models/report.py index bf3184f5..d6bcf17c 100644 --- a/bookwyrm/models/report.py +++ b/bookwyrm/models/report.py @@ -21,7 +21,7 @@ class Report(BookWyrmModel): links = models.ManyToManyField("Link", blank=True) resolved = models.BooleanField(default=False) - def get_remote_id(self): + def get_permalink(self): return f"https://{DOMAIN}/settings/reports/{self.id}" class Meta: diff --git a/bookwyrm/models/shelf.py b/bookwyrm/models/shelf.py index 320d495d..749049e3 100644 --- a/bookwyrm/models/shelf.py +++ b/bookwyrm/models/shelf.py @@ -59,7 +59,7 @@ class Shelf(OrderedCollectionMixin, BookWyrmModel): """can the shelf be safely deleted?""" return self.editable and not self.shelfbook_set.exists() - def get_remote_id(self): + def get_permalink(self): """shelf identifier instead of id""" base_path = self.user.remote_id identifier = self.identifier or self.get_identifier() diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index be5c1992..65ecad9e 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -396,7 +396,7 @@ class KeyPair(ActivitypubMixin, BookWyrmModel): activity_serializer = activitypub.PublicKey serialize_reverse_fields = [("owner", "owner", "id")] - def get_remote_id(self): + def get_permalink(self): # self.owner is set by the OneToOneField on User return f"{self.owner.remote_id}/#main-key" @@ -430,7 +430,7 @@ class AnnualGoal(BookWyrmModel): unique_together = ("user", "year") - def get_remote_id(self): + def get_permalink(self): """put the year in the path""" return f"{self.user.remote_id}/goal/{self.year}" diff --git a/bookwyrm/templates/book/book.html b/bookwyrm/templates/book/book.html index 0e2fd5d3..9154fd78 100644 --- a/bookwyrm/templates/book/book.html +++ b/bookwyrm/templates/book/book.html @@ -267,7 +267,7 @@ {% if user_statuses.review_count or user_statuses.comment_count or user_statuses.quotation_count %}