forked from mirrors/bookwyrm
775470a852
Some isnis are imported with spaces and some not. This normalises them for comparison and creating URLs
201 lines
7.8 KiB
Python
201 lines
7.8 KiB
Python
""" the good stuff! the books! """
|
|
from dateutil.parser import parse as dateparse
|
|
from re import sub
|
|
from django.contrib.auth.decorators import login_required, permission_required
|
|
from django.contrib.postgres.search import SearchRank, SearchVector
|
|
from django.db import transaction
|
|
from django.http import HttpResponseBadRequest
|
|
from django.shortcuts import get_object_or_404, redirect
|
|
from django.template.response import TemplateResponse
|
|
from django.utils.datastructures import MultiValueDictKeyError
|
|
from django.utils.decorators import method_decorator
|
|
from django.views import View
|
|
|
|
from bookwyrm import book_search, forms, models
|
|
from bookwyrm.utils.isni import find_authors_by_name, build_author_dict
|
|
from bookwyrm.views.helpers import get_edition
|
|
from .books import set_cover_from_url
|
|
|
|
# pylint: disable=no-self-use
|
|
@method_decorator(login_required, name="dispatch")
|
|
@method_decorator(
|
|
permission_required("bookwyrm.edit_book", raise_exception=True), name="dispatch"
|
|
)
|
|
class EditBook(View):
|
|
"""edit a book"""
|
|
|
|
def get(self, request, book_id=None):
|
|
"""info about a book"""
|
|
book = None
|
|
if book_id:
|
|
book = get_edition(book_id)
|
|
if not book.description:
|
|
book.description = book.parent_work.description
|
|
data = {"book": book, "form": forms.EditionForm(instance=book)}
|
|
return TemplateResponse(request, "book/edit/edit_book.html", data)
|
|
|
|
# pylint: disable=too-many-locals
|
|
def post(self, request, book_id=None):
|
|
"""edit a book cool"""
|
|
# returns None if no match is found
|
|
book = models.Edition.objects.filter(id=book_id).first()
|
|
form = forms.EditionForm(request.POST, request.FILES, instance=book)
|
|
|
|
data = {"book": book, "form": form}
|
|
if not form.is_valid():
|
|
return TemplateResponse(request, "book/edit/edit_book.html", data)
|
|
|
|
add_author = request.POST.get("add_author")
|
|
# we're adding an author through a free text field
|
|
if add_author:
|
|
data["add_author"] = add_author
|
|
data["author_matches"] = []
|
|
data["isni_matches"] = []
|
|
for author in add_author.split(","):
|
|
if not author:
|
|
continue
|
|
# check for existing authors
|
|
vector = SearchVector("name", weight="A") + SearchVector(
|
|
"aliases", weight="B"
|
|
)
|
|
|
|
author_matches = (
|
|
models.Author.objects.annotate(search=vector)
|
|
.annotate(rank=SearchRank(vector, author))
|
|
.filter(rank__gt=0.4)
|
|
.order_by("-rank")[:5]
|
|
)
|
|
|
|
isni_authors = find_authors_by_name(
|
|
author
|
|
) # find matches from ISNI API
|
|
|
|
# do not show isni results for authors we already have in the DB
|
|
exists = [
|
|
i
|
|
for i in isni_authors
|
|
for a in author_matches
|
|
if sub("\D","",str(i["isni"])) == sub("\D","",str(a.isni))
|
|
]
|
|
|
|
# pylint: disable=cell-var-from-loop
|
|
isni_matches = list(filter(lambda x: x not in exists, isni_authors))
|
|
|
|
data["author_matches"].append(
|
|
{
|
|
"name": author.strip(),
|
|
"matches": author_matches,
|
|
"existing_isnis": exists,
|
|
"isni_matches": isni_matches,
|
|
}
|
|
)
|
|
|
|
# we're creating a new book
|
|
if not book:
|
|
# check if this is an edition of an existing work
|
|
author_text = book.author_text if book else add_author
|
|
data["book_matches"] = book_search.search(
|
|
f'{form.cleaned_data.get("title")} {author_text}',
|
|
min_confidence=0.5,
|
|
)[:5]
|
|
|
|
# either of the above cases requires additional confirmation
|
|
if add_author or not book:
|
|
# creting a book or adding an author to a book needs another step
|
|
data["confirm_mode"] = True
|
|
# this isn't preserved because it isn't part of the form obj
|
|
data["remove_authors"] = request.POST.getlist("remove_authors")
|
|
data["cover_url"] = request.POST.get("cover-url")
|
|
|
|
# make sure the dates are passed in as datetime, they're currently a string
|
|
# QueryDicts are immutable, we need to copy
|
|
formcopy = data["form"].data.copy()
|
|
try:
|
|
formcopy["first_published_date"] = dateparse(
|
|
formcopy["first_published_date"]
|
|
)
|
|
except (MultiValueDictKeyError, ValueError):
|
|
pass
|
|
try:
|
|
formcopy["published_date"] = dateparse(formcopy["published_date"])
|
|
except (MultiValueDictKeyError, ValueError):
|
|
pass
|
|
data["form"].data = formcopy
|
|
return TemplateResponse(request, "book/edit/edit_book.html", data)
|
|
|
|
remove_authors = request.POST.getlist("remove_authors")
|
|
for author_id in remove_authors:
|
|
book.authors.remove(author_id)
|
|
|
|
book = form.save(commit=False)
|
|
url = request.POST.get("cover-url")
|
|
if url:
|
|
image = set_cover_from_url(url)
|
|
if image:
|
|
book.cover.save(*image, save=False)
|
|
book.save()
|
|
return redirect(f"/book/{book.id}")
|
|
|
|
|
|
@method_decorator(login_required, name="dispatch")
|
|
@method_decorator(
|
|
permission_required("bookwyrm.edit_book", raise_exception=True), name="dispatch"
|
|
)
|
|
class ConfirmEditBook(View):
|
|
"""confirm edits to a book"""
|
|
|
|
def post(self, request, book_id=None):
|
|
"""edit a book cool"""
|
|
# returns None if no match is found
|
|
book = models.Edition.objects.filter(id=book_id).first()
|
|
form = forms.EditionForm(request.POST, request.FILES, instance=book)
|
|
|
|
data = {"book": book, "form": form}
|
|
if not form.is_valid():
|
|
return TemplateResponse(request, "book/edit/edit_book.html", data)
|
|
|
|
with transaction.atomic():
|
|
# save book
|
|
book = form.save()
|
|
|
|
# get or create author as needed
|
|
for i in range(int(request.POST.get("author-match-count", 0))):
|
|
match = request.POST.get(f"author_match-{i}")
|
|
if not match:
|
|
return HttpResponseBadRequest()
|
|
try:
|
|
# if it's an int, it's an ID
|
|
match = int(match)
|
|
author = get_object_or_404(
|
|
models.Author, id=request.POST[f"author_match-{i}"]
|
|
)
|
|
except ValueError:
|
|
# otherwise it's a name with or without isni id
|
|
author_data = build_author_dict(match)
|
|
author = models.Author.objects.create(**author_data)
|
|
book.authors.add(author)
|
|
|
|
# create work, if needed
|
|
if not book_id:
|
|
work_match = request.POST.get("parent_work")
|
|
if work_match and work_match != "0":
|
|
work = get_object_or_404(models.Work, id=work_match)
|
|
else:
|
|
work = models.Work.objects.create(title=form.cleaned_data["title"])
|
|
work.authors.set(book.authors.all())
|
|
book.parent_work = work
|
|
|
|
for author_id in request.POST.getlist("remove_authors"):
|
|
book.authors.remove(author_id)
|
|
|
|
# import cover, if requested
|
|
url = request.POST.get("cover-url")
|
|
if url:
|
|
image = set_cover_from_url(url)
|
|
if image:
|
|
book.cover.save(*image, save=False)
|
|
|
|
# we don't tell the world when creating a book
|
|
book.save(broadcast=False)
|
|
|
|
return redirect(f"/book/{book.id}")
|