Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Let a user search books within their shelves #3118

Merged
30 changes: 23 additions & 7 deletions bookwyrm/book_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def search(
min_confidence: float = 0,
filters: Optional[list[Any]] = None,
return_first: bool = False,
books: Optional[QuerySet[models.Edition]] = None,
) -> Union[Optional[models.Edition], QuerySet[models.Edition]]:
"""search your local database"""
filters = filters or []
Expand All @@ -54,13 +55,15 @@ def search(
# first, try searching unique identifiers
# unique identifiers never have spaces, title/author usually do
if not " " in query:
results = search_identifiers(query, *filters, return_first=return_first)
results = search_identifiers(
query, *filters, return_first=return_first, books=books
)

# if there were no identifier results...
if not results:
# then try searching title/author
results = search_title_author(
query, min_confidence, *filters, return_first=return_first
query, min_confidence, *filters, return_first=return_first, books=books
)
return results

Expand Down Expand Up @@ -98,9 +101,17 @@ def format_search_result(search_result):


def search_identifiers(
query, *filters, return_first=False
query,
*filters,
return_first=False,
books=None,
) -> Union[Optional[models.Edition], QuerySet[models.Edition]]:
"""tries remote_id, isbn; defined as dedupe fields on the model"""
"""search Editions by deduplication fields

Best for cases when we can assume someone is searching for an exact match on
commonly unique data identifiers like isbn or specific library ids.
"""
books = books or models.Edition.objects
if connectors.maybe_isbn(query):
# Oh did you think the 'S' in ISBN stood for 'standard'?
normalized_isbn = query.strip().upper().rjust(10, "0")
Expand All @@ -111,7 +122,7 @@ def search_identifiers(
for f in models.Edition._meta.get_fields()
if hasattr(f, "deduplication_field") and f.deduplication_field
]
results = models.Edition.objects.filter(
results = books.filter(
*filters, reduce(operator.or_, (Q(**f) for f in or_filters))
).distinct()

Expand All @@ -121,12 +132,17 @@ def search_identifiers(


def search_title_author(
query, min_confidence, *filters, return_first=False
query,
min_confidence,
*filters,
return_first=False,
books=None,
) -> QuerySet[models.Edition]:
"""searches for title and author"""
books = books or models.Edition.objects
query = SearchQuery(query, config="simple") | SearchQuery(query, config="english")
results = (
models.Edition.objects.filter(*filters, search_vector=query)
books.filter(*filters, search_vector=query)
.annotate(rank=SearchRank(F("search_vector"), query))
.filter(rank__gt=min_confidence)
.order_by("-rank")
Expand Down
15 changes: 13 additions & 2 deletions bookwyrm/templates/shelf/shelf.html
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ <h2 class="title is-3">
{% plural %}
{{ formatted_count }} books
{% endblocktrans %}

{% if books.has_other_pages %}
{% blocktrans trimmed with start=books.start_index end=books.end_index %}
(showing {{ start }}-{{ end }})
Expand All @@ -111,6 +110,8 @@ <h2 class="title is-3">
{% endif %}
{% endwith %}
</h2>
{% include 'shelf/shelves_filters.html' with user=user query=query %}

</div>
{% if is_self and shelf.id %}
<div class="column is-narrow">
Expand Down Expand Up @@ -209,7 +210,17 @@ <h2 class="title is-3">
</tbody>
</table>
{% else %}
<p><em>{% trans "This shelf is empty." %}</em></p>
<p>
<em>
{% if shelves_filter_query %}
{% blocktrans trimmed %}
We couldn't find any books that matched {{ shelves_filter_query }}
{% endblocktrans %}
{% else %}
{% trans "This shelf is empty." %}
{% endif %}
</em>
</p>
{% endif %}

</div>
Expand Down
9 changes: 9 additions & 0 deletions bookwyrm/templates/shelf/shelves_filter_field.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{% extends 'snippets/filters_panel/filter_field.html' %}
{% load i18n %}

{% block filter %}
<div class="control">
<label class="label" for="filter_query">{% trans 'Filter by keyword' %}</label>
<input aria-label="Filter by keyword" id="my-books-filter" class="input" type="text" name="filter" placeholder="{% trans 'Enter text here' %}" value="{{ shelves_filter_query|default:'' }}" spellcheck="false" />
</div>
{% endblock %}
5 changes: 5 additions & 0 deletions bookwyrm/templates/shelf/shelves_filters.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{% extends 'snippets/filters_panel/filters_panel.html' %}

{% block filter_fields %}
{% include 'shelf/shelves_filter_field.html' %}
{% endblock %}
6 changes: 3 additions & 3 deletions bookwyrm/views/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def get(self, request):
def api_book_search(request):
"""Return books via API response"""
query = request.GET.get("q")
query = isbn_check(query)
query = isbn_check_and_format(query)
min_confidence = request.GET.get("min_confidence", 0)
# only return local book results via json so we don't cascade
book_results = search(query, min_confidence=min_confidence)
Expand All @@ -64,7 +64,7 @@ def book_search(request):
"""the real business is elsewhere"""
query = request.GET.get("q")
# check if query is isbn
query = isbn_check(query)
query = isbn_check_and_format(query)
min_confidence = request.GET.get("min_confidence", 0)
search_remote = request.GET.get("remote", False) and request.user.is_authenticated

Expand Down Expand Up @@ -159,7 +159,7 @@ def list_search(request):
return TemplateResponse(request, "search/list.html", data)


def isbn_check(query):
def isbn_check_and_format(query):
"""isbn10 or isbn13 check, if so remove separators"""
if query:
su_num = re.sub(r"(?<=\d)\D(?=\d|[xX])", "", query)
Expand Down
11 changes: 11 additions & 0 deletions bookwyrm/views/shelf/shelf.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@
from bookwyrm.activitypub import ActivitypubResponse
from bookwyrm.settings import PAGE_LENGTH
from bookwyrm.views.helpers import is_api_request, get_user_from_username
from bookwyrm.book_search import search


# pylint: disable=no-self-use
class Shelf(View):
"""shelf page"""

# pylint: disable=R0914
def get(self, request, username, shelf_identifier=None):
"""display a shelf"""
user = get_user_from_username(request.user, username)
Expand All @@ -32,6 +34,8 @@ def get(self, request, username, shelf_identifier=None):
else:
shelves = models.Shelf.privacy_filter(request.user).filter(user=user).all()

shelves_filter_query = request.GET.get("filter")

# get the shelf and make sure the logged in user should be able to see it
if shelf_identifier:
shelf = get_object_or_404(user.shelf_set, identifier=shelf_identifier)
Expand All @@ -42,6 +46,7 @@ def get(self, request, username, shelf_identifier=None):
FakeShelf = namedtuple(
"Shelf", ("identifier", "name", "user", "books", "privacy")
)

books = (
models.Edition.viewer_aware_objects(request.user)
.filter(
Expand All @@ -50,6 +55,7 @@ def get(self, request, username, shelf_identifier=None):
)
.distinct()
)

shelf = FakeShelf("all", _("All books"), user, books, "public")

if is_api_request(request) and shelf_identifier:
Expand Down Expand Up @@ -86,6 +92,9 @@ def get(self, request, username, shelf_identifier=None):

books = sort_books(books, request.GET.get("sort"))

if shelves_filter_query:
books = search(shelves_filter_query, books=books)

paginated = Paginator(
books,
PAGE_LENGTH,
Expand All @@ -103,6 +112,8 @@ def get(self, request, username, shelf_identifier=None):
"page_range": paginated.get_elided_page_range(
page.number, on_each_side=2, on_ends=1
),
"shelves_filter_query": shelves_filter_query,
rosschapman marked this conversation as resolved.
Show resolved Hide resolved
"size": "small",
}

return TemplateResponse(request, "shelf/shelf.html", data)
Expand Down
Loading