Пример #1
0
    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}")
Пример #2
0
 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)
Пример #3
0
    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}">',
        )
Пример #4
0
    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)
Пример #5
0
    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())
Пример #6
0
    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}")
Пример #7
0
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)
Пример #8
0
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)
Пример #9
0
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)
Пример #10
0
 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')
Пример #11
0
 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")
Пример #12
0
    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")
Пример #13
0
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)
Пример #14
0
    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")
Пример #15
0
    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())
Пример #16
0
    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)
Пример #17
0
    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)
Пример #18
0
    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")
Пример #19
0
    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)
Пример #20
0
 def get(self, request):
     """info about a book"""
     data = {"form": forms.EditionForm()}
     return TemplateResponse(request, "book/edit/edit_book.html", data)
Пример #21
0
    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)
Пример #22
0
    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}")
Пример #23
0
    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}")