mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-11-21 17:11:06 +00:00
Merge branch 'main' into mypy-utils
This commit is contained in:
commit
6e9f64262c
8 changed files with 116 additions and 32 deletions
|
@ -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]:
|
||||
|
|
|
@ -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(
|
||||
|
@ -374,16 +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, ""]:
|
||||
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)
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
""" bookwyrm settings and configuration """
|
||||
import os
|
||||
from typing import AnyStr
|
||||
|
||||
from environs import Env
|
||||
|
||||
import requests
|
||||
|
@ -12,7 +14,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",
|
||||
|
@ -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"),
|
||||
]
|
||||
|
|
|
@ -3,14 +3,13 @@
|
|||
xmlns="http://a9.com/-/spec/opensearch/1.1/"
|
||||
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 %}
|
||||
{{ site_name }} search
|
||||
{% endblocktrans %}</Description>
|
||||
<Image width="16" height="16" type="image/x-icon">{{ image }}</Image>
|
||||
<Url
|
||||
type="text/html"
|
||||
method="get"
|
||||
template="https://{{ DOMAIN }}{% url 'search' %}?q={searchTerms}"
|
||||
/>
|
||||
</OpenSearchDescription>
|
||||
|
|
31
bookwyrm/tests/test_isbn.py
Normal file
31
bookwyrm/tests/test_isbn.py
Normal 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")
|
|
@ -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)
|
||||
|
|
|
@ -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}
|
||||
|
|
3
mypy.ini
3
mypy.ini
|
@ -19,6 +19,9 @@ ignore_errors = False
|
|||
[mypy-bookwyrm.importers.*]
|
||||
ignore_errors = False
|
||||
|
||||
[mypy-bookwyrm.isbn.*]
|
||||
ignore_errors = False
|
||||
|
||||
[mypy-celerywyrm.*]
|
||||
ignore_errors = False
|
||||
|
||||
|
|
Loading…
Reference in a new issue