Merge branch 'main' into mypy-utils

This commit is contained in:
Joeri de Ruiter 2023-09-08 08:53:38 +02:00 committed by GitHub
commit 6e9f64262c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 116 additions and 32 deletions

View file

@ -1,11 +1,20 @@
""" Use the range message from isbn-international to hyphenate ISBNs """ """ Use the range message from isbn-international to hyphenate ISBNs """
import os import os
from typing import Optional
from xml.etree import ElementTree from xml.etree import ElementTree
from xml.etree.ElementTree import Element
import requests import requests
from bookwyrm import settings 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 IsbnHyphenator:
"""Class to manage the range message xml file and use it to hyphenate ISBNs""" """Class to manage the range message xml file and use it to hyphenate ISBNs"""
@ -15,58 +24,94 @@ class IsbnHyphenator:
) )
__element_tree = None __element_tree = None
def update_range_message(self): def update_range_message(self) -> None:
"""Download the range message xml file and save it locally""" """Download the range message xml file and save it locally"""
response = requests.get(self.__range_message_url) response = requests.get(self.__range_message_url)
with open(self.__range_file_path, "w", encoding="utf-8") as file: with open(self.__range_file_path, "w", encoding="utf-8") as file:
file.write(response.text) file.write(response.text)
self.__element_tree = None 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""" """hyphenate the given ISBN-13 number using the range message"""
if isbn_13 is None: if isbn_13 is None:
return None return None
if self.__element_tree is None: if self.__element_tree is None:
self.__element_tree = ElementTree.parse(self.__range_file_path) self.__element_tree = ElementTree.parse(self.__range_file_path)
gs1_prefix = isbn_13[:3] gs1_prefix = isbn_13[:3]
reg_group = self.__find_reg_group(isbn_13, gs1_prefix) reg_group = self.__find_reg_group(isbn_13, gs1_prefix)
if reg_group is None: if reg_group is None:
return isbn_13 # failed to hyphenate return isbn_13 # failed to hyphenate
registrant = self.__find_registrant(isbn_13, gs1_prefix, reg_group) registrant = self.__find_registrant(isbn_13, gs1_prefix, reg_group)
if registrant is None: if registrant is None:
return isbn_13 # failed to hyphenate return isbn_13 # failed to hyphenate
publication = isbn_13[len(gs1_prefix) + len(reg_group) + len(registrant) : -1] publication = isbn_13[len(gs1_prefix) + len(reg_group) + len(registrant) : -1]
check_digit = isbn_13[-1:] check_digit = isbn_13[-1:]
return "-".join((gs1_prefix, reg_group, registrant, publication, check_digit)) return "-".join((gs1_prefix, reg_group, registrant, publication, check_digit))
def __find_reg_group(self, isbn_13, gs1_prefix): def __find_reg_group(self, isbn_13: str, gs1_prefix: str) -> Optional[str]:
for ean_ucc_el in self.__element_tree.find("EAN.UCCPrefixes").findall( if self.__element_tree is None:
"EAN.UCC" self.__element_tree = ElementTree.parse(self.__range_file_path)
):
if ean_ucc_el.find("Prefix").text == gs1_prefix: ucc_prefixes_el = self.__element_tree.find("EAN.UCCPrefixes")
for rule_el in ean_ucc_el.find("Rules").findall("Rule"): if ucc_prefixes_el is None:
length = int(rule_el.find("Length").text) 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: if length == 0:
continue 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] reg_group = isbn_13[len(gs1_prefix) : len(gs1_prefix) + length]
if reg_grp_range[0] <= int(reg_group) <= reg_grp_range[1]: if reg_grp_range[0] <= int(reg_group) <= reg_grp_range[1]:
return reg_group return reg_group
return None return None
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) 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)): if self.__element_tree is None:
for rule_el in group_el.find("Rules").findall("Rule"): self.__element_tree = ElementTree.parse(self.__range_file_path)
length = int(rule_el.find("Length").text)
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: if length == 0:
continue continue
range_el = rule_el.find("Range")
if range_el is None or range_el.text is None:
continue
registrant_range = [ 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] registrant = isbn_13[from_ind : from_ind + length]
if registrant_range[0] <= int(registrant) <= registrant_range[1]: if registrant_range[0] <= int(registrant) <= registrant_range[1]:

View file

@ -217,6 +217,13 @@ class Book(BookDataModel):
"""editions and works both use "book" instead of model_name""" """editions and works both use "book" instead of model_name"""
return f"https://{DOMAIN}/book/{self.id}" 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): def __repr__(self):
# pylint: disable=consider-using-f-string # pylint: disable=consider-using-f-string
return "<{} key={!r} title={!r}>".format( return "<{} key={!r} title={!r}>".format(
@ -374,16 +381,7 @@ class Edition(Book):
# Create sort title by removing articles from title # Create sort title by removing articles from title
if self.sort_title in [None, ""]: if self.sort_title in [None, ""]:
if self.sort_title in [None, ""]: self.sort_title = self.guess_sort_title()
articles = chain(
*(
LANGUAGE_ARTICLES.get(language, ())
for language in tuple(self.languages)
)
)
self.sort_title = re.sub(
f'^{" |^".join(articles)} ', "", str(self.title).lower()
)
return super().save(*args, **kwargs) return super().save(*args, **kwargs)

View file

@ -1,5 +1,7 @@
""" bookwyrm settings and configuration """ """ bookwyrm settings and configuration """
import os import os
from typing import AnyStr
from environs import Env from environs import Env
import requests import requests
@ -12,7 +14,7 @@ from django.core.exceptions import ImproperlyConfigured
env = Env() env = Env()
env.read_env() env.read_env()
DOMAIN = env("DOMAIN") DOMAIN = env("DOMAIN")
VERSION = "0.6.4" VERSION = "0.6.5"
RELEASE_API = env( RELEASE_API = env(
"RELEASE_API", "RELEASE_API",
@ -37,7 +39,7 @@ EMAIL_SENDER_DOMAIN = env("EMAIL_SENDER_DOMAIN", DOMAIN)
EMAIL_SENDER = f"{EMAIL_SENDER_NAME}@{EMAIL_SENDER_DOMAIN}" EMAIL_SENDER = f"{EMAIL_SENDER_NAME}@{EMAIL_SENDER_DOMAIN}"
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) # 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 = [ LOCALE_PATHS = [
os.path.join(BASE_DIR, "locale"), os.path.join(BASE_DIR, "locale"),
] ]

View file

@ -3,14 +3,13 @@
xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns="http://a9.com/-/spec/opensearch/1.1/"
xmlns:moz="http://www.mozilla.org/2006/browser/search/" xmlns:moz="http://www.mozilla.org/2006/browser/search/"
> >
<ShortName>{{ site_name }}</ShortName> <ShortName>{{ site.name }}</ShortName>
<Description>{% blocktrans trimmed with site_name=site.name %} <Description>{% blocktrans trimmed with site_name=site.name %}
{{ site_name }} search {{ site_name }} search
{% endblocktrans %}</Description> {% endblocktrans %}</Description>
<Image width="16" height="16" type="image/x-icon">{{ image }}</Image> <Image width="16" height="16" type="image/x-icon">{{ image }}</Image>
<Url <Url
type="text/html" type="text/html"
method="get"
template="https://{{ DOMAIN }}{% url 'search' %}?q={searchTerms}" template="https://{{ DOMAIN }}{% url 'search' %}?q={searchTerms}"
/> />
</OpenSearchDescription> </OpenSearchDescription>

View file

@ -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")

View file

@ -5,6 +5,7 @@ from django.shortcuts import get_object_or_404, redirect
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views import View from django.views import View
from django.views.decorators.http import require_POST
from bookwyrm import forms, models from bookwyrm import forms, models
from bookwyrm.settings import PAGE_LENGTH from bookwyrm.settings import PAGE_LENGTH
@ -108,6 +109,7 @@ class EditAnnouncement(View):
@login_required @login_required
@permission_required("bookwyrm.edit_instance_settings", raise_exception=True) @permission_required("bookwyrm.edit_instance_settings", raise_exception=True)
@require_POST
def delete_announcement(_, announcement_id): def delete_announcement(_, announcement_id):
"""delete announcement""" """delete announcement"""
announcement = get_object_or_404(models.Announcement, id=announcement_id) announcement = get_object_or_404(models.Announcement, id=announcement_id)

View file

@ -32,6 +32,9 @@ class EditBook(View):
def get(self, request, book_id): def get(self, request, book_id):
"""info about a book""" """info about a book"""
book = get_edition(book_id) 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: if not book.description:
book.description = book.parent_work.description book.description = book.parent_work.description
data = {"book": book, "form": forms.EditionForm(instance=book)} data = {"book": book, "form": forms.EditionForm(instance=book)}
@ -40,6 +43,7 @@ class EditBook(View):
def post(self, request, book_id): def post(self, request, book_id):
"""edit a book cool""" """edit a book cool"""
book = get_object_or_404(models.Edition, id=book_id) book = get_object_or_404(models.Edition, id=book_id)
form = forms.EditionForm(request.POST, request.FILES, instance=book) form = forms.EditionForm(request.POST, request.FILES, instance=book)
data = {"book": book, "form": form} data = {"book": book, "form": form}

View file

@ -19,6 +19,9 @@ ignore_errors = False
[mypy-bookwyrm.importers.*] [mypy-bookwyrm.importers.*]
ignore_errors = False ignore_errors = False
[mypy-bookwyrm.isbn.*]
ignore_errors = False
[mypy-celerywyrm.*] [mypy-celerywyrm.*]
ignore_errors = False ignore_errors = False