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} if not form.is_valid(): return TemplateResponse(request, "book/edit/edit_book.html", data) data = add_authors(request, data) # either of the above cases requires additional confirmation if data.get("add_author"): 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}")
def get(self, request, book_id): """info about a book""" 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)
def test_date_regression(self): """ensure that creating a new book actually saves the published date fields this was initially a regression due to using a custom date picker tag """ first_published_date = "2021-04-20" published_date = "2022-04-20" self.local_user.groups.add(self.group) view = views.EditBook.as_view() form = forms.EditionForm( { "title": "New Title", "last_edited_by": self.local_user.id, "first_published_date": first_published_date, "published_date": published_date, } ) request = self.factory.post("", form.data) request.user = self.local_user with patch("bookwyrm.connectors.connector_manager.local_search"): result = view(request) result.render() self.assertContains( result, f'<input type="date" name="first_published_date" class="input" id="id_first_published_date" value="{first_published_date}">', ) self.assertContains( result, f'<input type="date" name="published_date" class="input" id="id_published_date" value="{published_date}">', )
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, "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"] = [] for author in add_author.split(","): if not author: continue # check for existing authors vector = SearchVector("name", weight="A") + SearchVector( "aliases", weight="B") data["author_matches"].append({ "name": author.strip(), "matches": (models.Author.objects.annotate(search=vector).annotate( rank=SearchRank(vector, author)).filter( rank__gt=0.4).order_by("-rank")[:5]), }) print(data["author_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"] = connector_manager.local_search( "%s %s" % (form.cleaned_data.get("title"), author_text), min_confidence=0.5, raw=True, )[: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") return TemplateResponse(request, "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() return redirect("/book/%s" % book.id)
def test_edit_book_remove_author(self): """remove an author from a book""" author = models.Author.objects.create(name="Sappho") self.book.authors.add(author) form = forms.EditionForm(instance=self.book) view = views.EditBook.as_view() self.local_user.groups.add(self.group) form = forms.EditionForm(instance=self.book) form.data["title"] = "New Title" form.data["last_edited_by"] = self.local_user.id form.data["remove_authors"] = [author.id] request = self.factory.post("", form.data) request.user = self.local_user with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): view(request, self.book.id) self.book.refresh_from_db() self.assertEqual(self.book.title, "New Title") self.assertFalse(self.book.authors.exists())
def post(self, request): """create a new book""" # returns None if no match is found form = forms.EditionForm(request.POST, request.FILES) data = {"form": form} # collect data provided by the work or import item parent_work_id = request.POST.get("parent_work") authors = None if request.POST.get("authors"): author_ids = findall(r"\d+", request.POST["authors"]) authors = models.Author.objects.filter(id__in=author_ids) # fake book in case we need to keep editing if parent_work_id: data["book"] = { "parent_work": { "id": parent_work_id }, "authors": authors, } if not form.is_valid(): return TemplateResponse(request, "book/edit/edit_book.html", data) data = add_authors(request, data) # check if this is an edition of an existing work author_text = ", ".join(data.get("add_author", [])) data["book_matches"] = book_search.search( f'{form.cleaned_data.get("title")} {author_text}', min_confidence=0.1, )[:5] # go to confirm mode if not parent_work_id or data.get("add_author"): return TemplateResponse(request, "book/edit/edit_book.html", data) with transaction.atomic(): book = form.save() parent_work = get_object_or_404(models.Work, id=parent_work_id) book.parent_work = parent_work if authors: book.authors.add(*authors) 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}")
def edit_book_page(request, book_id): ''' info about a book ''' book = books_manager.get_edition(book_id) if not book.description: book.description = book.parent_work.description data = { 'title': 'Edit Book', 'book': book, 'form': forms.EditionForm(instance=book) } return TemplateResponse(request, 'edit_book.html', data)
def edit_book(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) if not form.is_valid(): data = {'title': 'Edit Book', 'book': book, 'form': form} return TemplateResponse(request, 'edit_book.html', data) form.save() outgoing.handle_update_book(request.user, book) return redirect('/book/%s' % book.id)
def create_book_from_data(request): """create a book with starter data""" author_ids = findall(r"\d+", request.POST.get("authors")) book = { "parent_work": { "id": request.POST.get("parent_work") }, "authors": models.Author.objects.filter(id__in=author_ids).all(), "subjects": request.POST.getlist("subjects"), } data = {"book": book, "form": forms.EditionForm(request.POST)} return TemplateResponse(request, "book/edit/edit_book.html", data)
def test_edit_book(self): ''' lets a user edit a book ''' view = views.EditBook.as_view() self.local_user.groups.add(self.group) form = forms.EditionForm(instance=self.book) form.data['title'] = 'New Title' form.data['last_edited_by'] = self.local_user.id request = self.factory.post('', form.data) request.user = self.local_user with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): view(request, self.book.id) self.book.refresh_from_db() self.assertEqual(self.book.title, 'New Title')
def test_edit_book(self): """lets a user edit a book""" view = views.EditBook.as_view() self.local_user.groups.add(self.group) form = forms.EditionForm(instance=self.book) form.data["title"] = "New Title" form.data["last_edited_by"] = self.local_user.id request = self.factory.post("", form.data) request.user = self.local_user with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): view(request, self.book.id) self.book.refresh_from_db() self.assertEqual(self.book.title, "New Title")
def test_create_book(self): """create an entirely new book and work""" view = views.ConfirmEditBook.as_view() self.local_user.groups.add(self.group) form = forms.EditionForm() form.data["title"] = "New Title" form.data["last_edited_by"] = self.local_user.id request = self.factory.post("", form.data) request.user = self.local_user view(request) book = models.Edition.objects.get(title="New Title") self.assertEqual(book.parent_work.title, "New Title")
def edit_book(request, book_id): ''' edit a book cool ''' if not request.method == 'POST': return redirect('/book/%s' % book_id) try: book = models.Edition.objects.get(id=book_id) except models.Edition.DoesNotExist: return HttpResponseNotFound() form = forms.EditionForm(request.POST, request.FILES, instance=book) if not form.is_valid(): return redirect(request.headers.get('Referer', '/')) form.save() outgoing.handle_update_book(request.user, book) return redirect('/book/%s' % book.id)
def test_edit_book_add_author(self): """lets a user edit a book with new authors""" view = views.EditBook.as_view() self.local_user.groups.add(self.group) form = forms.EditionForm(instance=self.book) form.data["title"] = "New Title" form.data["last_edited_by"] = self.local_user.id form.data["add_author"] = "Sappho" request = self.factory.post("", form.data) request.user = self.local_user result = view(request, self.book.id) validate_html(result.render()) # the changes haven't been saved yet self.book.refresh_from_db() self.assertEqual(self.book.title, "Example Edition")
def test_create_book_with_author(self): """create an entirely new book and work""" view = views.ConfirmEditBook.as_view() self.local_user.groups.add(self.group) form = forms.EditionForm() form.data["title"] = "New Title" form.data["author-match-count"] = "1" form.data["author_match-0"] = "Sappho" form.data["last_edited_by"] = self.local_user.id request = self.factory.post("", form.data) request.user = self.local_user view(request) book = models.Edition.objects.get(title="New Title") self.assertEqual(book.parent_work.title, "New Title") self.assertEqual(book.authors.first().name, "Sappho") self.assertEqual(book.authors.first(), book.parent_work.authors.first())
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_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("author_match-%d" % 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["author_match-%d" % i] ) except ValueError: # otherwise it's a name author = models.Author.objects.create(name=match) 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 # we don't tell the world when creating a book book.save(broadcast=False) for author_id in request.POST.getlist("remove_authors"): book.authors.remove(author_id) return redirect("/book/%s" % book.id)
def test_create_book_existing_work(self): """create an entirely new book and work""" view = views.ConfirmEditBook.as_view() self.local_user.groups.add(self.group) form = forms.EditionForm() form.data["title"] = "New Title" form.data["parent_work"] = self.work.id form.data["last_edited_by"] = self.local_user.id request = self.factory.post("", form.data) request.user = self.local_user with patch( "bookwyrm.preview_images.generate_edition_preview_image_task.delay" ): view(request) book = models.Edition.objects.get(title="New Title") self.assertEqual(book.parent_work, self.work)
def test_edit_book_add_new_author_confirm(self): """lets a user edit a book confirmed with new authors""" view = views.ConfirmEditBook.as_view() self.local_user.groups.add(self.group) form = forms.EditionForm(instance=self.book) form.data["title"] = "New Title" form.data["last_edited_by"] = self.local_user.id form.data["author-match-count"] = 1 form.data["author_match-0"] = "Sappho" request = self.factory.post("", form.data) request.user = self.local_user with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): view(request, self.book.id) self.book.refresh_from_db() self.assertEqual(self.book.title, "New Title") self.assertEqual(self.book.authors.first().name, "Sappho")
def test_create_book_upload_cover_url(self): """create an entirely new book and work with cover url""" self.assertFalse(self.book.cover) self.local_user.groups.add(self.group) cover_url = _setup_cover_url() form = forms.EditionForm() form.data["title"] = "New Title" form.data["last_edited_by"] = self.local_user.id form.data["cover-url"] = cover_url request = self.factory.post("", form.data) request.user = self.local_user with patch( "bookwyrm.models.activitypub_mixin.broadcast_task.apply_async" ) as delay_mock: views.upload_cover(request, self.book.id) self.assertEqual(delay_mock.call_count, 1) self.book.refresh_from_db() self.assertTrue(self.book.cover)
def get(self, request): """info about a book""" data = {"form": forms.EditionForm()} return TemplateResponse(request, "book/edit/edit_book.html", data)
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_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"] = [] for author in add_author.split(","): if not author: continue # check for existing authors vector = SearchVector("name", weight="A") + SearchVector( "aliases", weight="B" ) data["author_matches"].append( { "name": author.strip(), "matches": ( models.Author.objects.annotate(search=vector) .annotate(rank=SearchRank(vector, author)) .filter(rank__gt=0.4) .order_by("-rank")[:5] ), } ) print(data["author_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"] = connector_manager.local_search( "%s %s" % (form.cleaned_data.get("title"), author_text), min_confidence=0.5, raw=True, )[: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") # 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_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("/book/%s" % book.id)
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) # filter out empty author fields add_author = [ author for author in request.POST.getlist("add_author") if author ] if add_author: data["add_author"] = add_author data["author_matches"] = [] data["isni_matches"] = [] for author in add_author: 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, description=True) # find matches from ISNI API # dedupe isni authors we already have in the DB exists = [ i for i in isni_authors for a in author_matches if sub( r"\D", "", str(i.isni)) == sub(r"\D", "", str(a.isni)) ] # pylint: disable=cell-var-from-loop matches = list(filter(lambda x: x not in exists, isni_authors)) # combine existing and isni authors matches.extend(author_matches) data["author_matches"].append({ "name": author.strip(), "matches": matches, "existing_isnis": exists, }) # 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}")
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}"]) # update author metadata if the ISNI record is more complete isni = request.POST.get(f"isni-for-{match}", None) if isni is not None: augment_author_metadata(author, isni) except ValueError: # otherwise it's a new author isni_match = request.POST.get(f"author_match-{i}") author_object = build_author_from_isni(isni_match) # with author data class from isni id if "author" in author_object: skeleton = models.Author.objects.create( name=author_object["author"].name) author = author_object["author"].to_model( model=models.Author, overwrite=True, instance=skeleton) else: # or it's just a name author = models.Author.objects.create(name=match) 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}")