Merge pull request #1305 from bookwyrm-social/search-editions

Search editions
This commit is contained in:
Mouse Reeve 2021-08-17 13:26:20 -06:00 committed by GitHub
commit fc9613b975
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 238 additions and 107 deletions

View file

@ -1,6 +1,7 @@
{% extends 'snippets/filters_panel/filters_panel.html' %} {% extends 'snippets/filters_panel/filters_panel.html' %}
{% block filter_fields %} {% block filter_fields %}
{% include 'book/search_filter.html' %}
{% include 'book/language_filter.html' %} {% include 'book/language_filter.html' %}
{% include 'book/format_filter.html' %} {% include 'book/format_filter.html' %}
{% endblock %} {% endblock %}

View file

@ -0,0 +1,8 @@
{% extends 'snippets/filters_panel/filter_field.html' %}
{% load i18n %}
{% block filter %}
<label class="label" for="id_search">{% trans "Search editions" %}</label>
<input type="text" class="input" name="q" value="{{ request.GET.q|default:'' }}" id="id_search">
{% endblock %}

View file

@ -280,49 +280,6 @@ class BookViews(TestCase):
self.assertEqual(book.authors.first().name, "Sappho") self.assertEqual(book.authors.first().name, "Sappho")
self.assertEqual(book.authors.first(), book.parent_work.authors.first()) self.assertEqual(book.authors.first(), book.parent_work.authors.first())
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
def test_switch_edition(self, _):
"""updates user's relationships to a book"""
work = models.Work.objects.create(title="test work")
edition1 = models.Edition.objects.create(title="first ed", parent_work=work)
edition2 = models.Edition.objects.create(title="second ed", parent_work=work)
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
shelf = models.Shelf.objects.create(name="Test Shelf", user=self.local_user)
models.ShelfBook.objects.create(
book=edition1,
user=self.local_user,
shelf=shelf,
)
models.ReadThrough.objects.create(user=self.local_user, book=edition1)
self.assertEqual(models.ShelfBook.objects.get().book, edition1)
self.assertEqual(models.ReadThrough.objects.get().book, edition1)
request = self.factory.post("", {"edition": edition2.id})
request.user = self.local_user
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
views.switch_edition(request)
self.assertEqual(models.ShelfBook.objects.get().book, edition2)
self.assertEqual(models.ReadThrough.objects.get().book, edition2)
def test_editions_page(self):
"""there are so many views, this just makes sure it LOADS"""
view = views.Editions.as_view()
request = self.factory.get("")
with patch("bookwyrm.views.books.is_api_request") as is_api:
is_api.return_value = False
result = view(request, self.work.id)
self.assertIsInstance(result, TemplateResponse)
result.render()
self.assertEqual(result.status_code, 200)
request = self.factory.get("")
with patch("bookwyrm.views.books.is_api_request") as is_api:
is_api.return_value = True
result = view(request, self.work.id)
self.assertIsInstance(result, ActivitypubResponse)
self.assertEqual(result.status_code, 200)
def test_upload_cover_file(self): def test_upload_cover_file(self):
"""add a cover via file upload""" """add a cover via file upload"""
self.assertFalse(self.book.cover) self.assertFalse(self.book.cover)

View file

@ -0,0 +1,126 @@
""" test for app action functionality """
from unittest.mock import patch
from django.template.response import TemplateResponse
from django.test import TestCase
from django.test.client import RequestFactory
from bookwyrm import models, views
from bookwyrm.activitypub import ActivitypubResponse
class BookViews(TestCase):
"""books books books"""
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
self.work = models.Work.objects.create(title="Test Work")
self.book = models.Edition.objects.create(
title="Example Edition",
remote_id="https://example.com/book/1",
parent_work=self.work,
physical_format="paperback",
)
models.SiteSettings.objects.create()
def test_editions_page(self):
"""there are so many views, this just makes sure it LOADS"""
view = views.Editions.as_view()
request = self.factory.get("")
with patch("bookwyrm.views.editions.is_api_request") as is_api:
is_api.return_value = False
result = view(request, self.work.id)
self.assertIsInstance(result, TemplateResponse)
result.render()
self.assertEqual(result.status_code, 200)
self.assertTrue("paperback" in result.context_data["formats"])
def test_editions_page_filtered(self):
"""editions view with filters"""
models.Edition.objects.create(
title="Fish",
physical_format="okay",
parent_work=self.work,
)
view = views.Editions.as_view()
request = self.factory.get("")
with patch("bookwyrm.views.editions.is_api_request") as is_api:
is_api.return_value = False
result = view(request, self.work.id)
self.assertIsInstance(result, TemplateResponse)
result.render()
self.assertEqual(result.status_code, 200)
self.assertEqual(len(result.context_data["editions"].object_list), 2)
self.assertEqual(len(result.context_data["formats"]), 2)
self.assertTrue("paperback" in result.context_data["formats"])
self.assertTrue("okay" in result.context_data["formats"])
request = self.factory.get("", {"q": "fish"})
with patch("bookwyrm.views.editions.is_api_request") as is_api:
is_api.return_value = False
result = view(request, self.work.id)
result.render()
self.assertEqual(result.status_code, 200)
self.assertEqual(len(result.context_data["editions"].object_list), 1)
request = self.factory.get("", {"q": "okay"})
with patch("bookwyrm.views.editions.is_api_request") as is_api:
is_api.return_value = False
result = view(request, self.work.id)
result.render()
self.assertEqual(result.status_code, 200)
self.assertEqual(len(result.context_data["editions"].object_list), 1)
request = self.factory.get("", {"format": "okay"})
with patch("bookwyrm.views.editions.is_api_request") as is_api:
is_api.return_value = False
result = view(request, self.work.id)
result.render()
self.assertEqual(result.status_code, 200)
self.assertEqual(len(result.context_data["editions"].object_list), 1)
def test_editions_page_api(self):
"""there are so many views, this just makes sure it LOADS"""
view = views.Editions.as_view()
request = self.factory.get("")
with patch("bookwyrm.views.editions.is_api_request") as is_api:
is_api.return_value = True
result = view(request, self.work.id)
self.assertIsInstance(result, ActivitypubResponse)
self.assertEqual(result.status_code, 200)
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
def test_switch_edition(self, _):
"""updates user's relationships to a book"""
work = models.Work.objects.create(title="test work")
edition1 = models.Edition.objects.create(title="first ed", parent_work=work)
edition2 = models.Edition.objects.create(title="second ed", parent_work=work)
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
shelf = models.Shelf.objects.create(name="Test Shelf", user=self.local_user)
models.ShelfBook.objects.create(
book=edition1,
user=self.local_user,
shelf=shelf,
)
models.ReadThrough.objects.create(user=self.local_user, book=edition1)
self.assertEqual(models.ShelfBook.objects.get().book, edition1)
self.assertEqual(models.ReadThrough.objects.get().book, edition1)
request = self.factory.post("", {"edition": edition2.id})
request.user = self.local_user
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
views.switch_edition(request)
self.assertEqual(models.ShelfBook.objects.get().book, edition2)
self.assertEqual(models.ReadThrough.objects.get().book, edition2)

View file

@ -4,11 +4,12 @@ from .authentication import Login, Register, Logout
from .authentication import ConfirmEmail, ConfirmEmailCode, resend_link from .authentication import ConfirmEmail, ConfirmEmailCode, resend_link
from .author import Author, EditAuthor from .author import Author, EditAuthor
from .block import Block, unblock from .block import Block, unblock
from .books import Book, EditBook, ConfirmEditBook, Editions from .books import Book, EditBook, ConfirmEditBook
from .books import upload_cover, add_description, switch_edition, resolve_book from .books import upload_cover, add_description, resolve_book
from .directory import Directory from .directory import Directory
from .discover import Discover from .discover import Discover
from .edit_user import EditUser, DeleteUser from .edit_user import EditUser, DeleteUser
from .editions import Editions, switch_edition
from .federation import Federation, FederatedServer from .federation import Federation, FederatedServer
from .federation import AddFederatedServer, ImportServerBlocklist from .federation import AddFederatedServer, ImportServerBlocklist
from .federation import block_server, unblock_server from .federation import block_server, unblock_server

View file

@ -24,7 +24,7 @@ from bookwyrm.settings import PAGE_LENGTH
from .helpers import is_api_request, get_edition, privacy_filter from .helpers import is_api_request, get_edition, privacy_filter
# pylint: disable= no-self-use # pylint: disable=no-self-use
class Book(View): class Book(View):
"""a book! this is the stuff""" """a book! this is the stuff"""
@ -270,37 +270,6 @@ class ConfirmEditBook(View):
return redirect("/book/%s" % book.id) return redirect("/book/%s" % book.id)
class Editions(View):
"""list of editions"""
def get(self, request, book_id):
"""list of editions of a book"""
work = get_object_or_404(models.Work, id=book_id)
if is_api_request(request):
return ActivitypubResponse(work.to_edition_list(**request.GET))
filters = {}
if request.GET.get("language"):
filters["languages__contains"] = [request.GET.get("language")]
if request.GET.get("format"):
filters["physical_format__iexact"] = request.GET.get("format")
editions = work.editions.order_by("-edition_rank")
languages = set(sum([e.languages for e in editions], []))
paginated = Paginator(editions.filter(**filters), PAGE_LENGTH)
data = {
"editions": paginated.get_page(request.GET.get("page")),
"work": work,
"languages": languages,
"formats": set(
e.physical_format.lower() for e in editions if e.physical_format
),
}
return TemplateResponse(request, "book/editions.html", data)
@login_required @login_required
@require_POST @require_POST
def upload_cover(request, book_id): def upload_cover(request, book_id):
@ -363,33 +332,3 @@ def resolve_book(request):
book = connector.get_or_create_book(remote_id) book = connector.get_or_create_book(remote_id)
return redirect("book", book.id) return redirect("book", book.id)
@login_required
@require_POST
@transaction.atomic
def switch_edition(request):
"""switch your copy of a book to a different edition"""
edition_id = request.POST.get("edition")
new_edition = get_object_or_404(models.Edition, id=edition_id)
shelfbooks = models.ShelfBook.objects.filter(
book__parent_work=new_edition.parent_work, shelf__user=request.user
)
for shelfbook in shelfbooks.all():
with transaction.atomic():
models.ShelfBook.objects.create(
created_date=shelfbook.created_date,
user=shelfbook.user,
shelf=shelfbook.shelf,
book=new_edition,
)
shelfbook.delete()
readthroughs = models.ReadThrough.objects.filter(
book__parent_work=new_edition.parent_work, user=request.user
)
for readthrough in readthroughs.all():
readthrough.book = new_edition
readthrough.save()
return redirect("/book/%d" % new_edition.id)

View file

@ -0,0 +1,99 @@
""" the good stuff! the books! """
from functools import reduce
import operator
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
from django.db import transaction
from django.db.models import Q
from django.shortcuts import get_object_or_404, redirect
from django.template.response import TemplateResponse
from django.views import View
from django.views.decorators.http import require_POST
from bookwyrm import models
from bookwyrm.activitypub import ActivitypubResponse
from bookwyrm.settings import PAGE_LENGTH
from .helpers import is_api_request
# pylint: disable=no-self-use
class Editions(View):
"""list of editions"""
def get(self, request, book_id):
"""list of editions of a book"""
work = get_object_or_404(models.Work, id=book_id)
if is_api_request(request):
return ActivitypubResponse(work.to_edition_list(**request.GET))
filters = {}
if request.GET.get("language"):
filters["languages__contains"] = [request.GET.get("language")]
if request.GET.get("format"):
filters["physical_format__iexact"] = request.GET.get("format")
editions = work.editions.order_by("-edition_rank")
languages = set(sum(editions.values_list("languages", flat=True), []))
editions = editions.filter(**filters)
query = request.GET.get("q")
if query:
searchable_array_fields = ["languages", "publishers"]
searchable_fields = [
"title",
"physical_format",
"isbn_10",
"isbn_13",
"oclc_number",
"asin",
]
search_filter_entries = [
{f"{f}__icontains": query} for f in searchable_fields
] + [{f"{f}__iexact": query} for f in searchable_array_fields]
editions = editions.filter(
reduce(operator.or_, (Q(**f) for f in search_filter_entries))
)
paginated = Paginator(editions, PAGE_LENGTH)
data = {
"editions": paginated.get_page(request.GET.get("page")),
"work": work,
"languages": languages,
"formats": set(
e.physical_format.lower() for e in editions if e.physical_format
),
}
return TemplateResponse(request, "book/editions.html", data)
@login_required
@require_POST
@transaction.atomic
def switch_edition(request):
"""switch your copy of a book to a different edition"""
edition_id = request.POST.get("edition")
new_edition = get_object_or_404(models.Edition, id=edition_id)
shelfbooks = models.ShelfBook.objects.filter(
book__parent_work=new_edition.parent_work, shelf__user=request.user
)
for shelfbook in shelfbooks.all():
with transaction.atomic():
models.ShelfBook.objects.create(
created_date=shelfbook.created_date,
user=shelfbook.user,
shelf=shelfbook.shelf,
book=new_edition,
)
shelfbook.delete()
readthroughs = models.ReadThrough.objects.filter(
book__parent_work=new_edition.parent_work, user=request.user
)
for readthrough in readthroughs.all():
readthrough.book = new_edition
readthrough.save()
return redirect("/book/%d" % new_edition.id)