Checkpoint

This commit is contained in:
Ross Chapman 2023-11-27 15:03:59 -08:00
parent 90cc28986e
commit d93da4e86d
8 changed files with 66 additions and 21 deletions

View file

@ -43,6 +43,7 @@ def search(
min_confidence: float = 0, min_confidence: float = 0,
filters: Optional[list[Any]] = None, filters: Optional[list[Any]] = None,
return_first: bool = False, return_first: bool = False,
books = None
) -> Union[Optional[models.Edition], QuerySet[models.Edition]]: ) -> Union[Optional[models.Edition], QuerySet[models.Edition]]:
"""search your local database""" """search your local database"""
filters = filters or [] filters = filters or []
@ -54,17 +55,16 @@ def search(
# first, try searching unique identifiers # first, try searching unique identifiers
# unique identifiers never have spaces, title/author usually do # unique identifiers never have spaces, title/author usually do
if not " " in query: 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 there were no identifier results...
if not results: if not results:
# then try searching title/author # then try searching title/author
results = search_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 return results
def isbn_search(query): def isbn_search(query):
"""search your local database""" """search your local database"""
if not query: if not query:
@ -98,8 +98,9 @@ def format_search_result(search_result):
def search_identifiers( def search_identifiers(
query, *filters, return_first=False query, *filters, return_first=False, books=None,
) -> Union[Optional[models.Edition], QuerySet[models.Edition]]: ) -> Union[Optional[models.Edition], QuerySet[models.Edition]]:
books = books or models.Edition
"""tries remote_id, isbn; defined as dedupe fields on the model""" """tries remote_id, isbn; defined as dedupe fields on the model"""
if connectors.maybe_isbn(query): if connectors.maybe_isbn(query):
# Oh did you think the 'S' in ISBN stood for 'standard'? # Oh did you think the 'S' in ISBN stood for 'standard'?
@ -111,7 +112,7 @@ def search_identifiers(
for f in models.Edition._meta.get_fields() for f in models.Edition._meta.get_fields()
if hasattr(f, "deduplication_field") and f.deduplication_field 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)) *filters, reduce(operator.or_, (Q(**f) for f in or_filters))
).distinct() ).distinct()
@ -121,12 +122,17 @@ def search_identifiers(
def search_title_author( def search_title_author(
query, min_confidence, *filters, return_first=False query,
min_confidence,
*filters,
return_first=False,
books=None,
) -> QuerySet[models.Edition]: ) -> QuerySet[models.Edition]:
"""searches for title and author""" """searches for title and author"""
books = books or models.Edition.objects
query = SearchQuery(query, config="simple") | SearchQuery(query, config="english") query = SearchQuery(query, config="simple") | SearchQuery(query, config="english")
results = ( results = (
models.Edition.objects.filter(*filters, search_vector=query) books.filter(*filters, search_vector=query)
.annotate(rank=SearchRank(F("search_vector"), query)) .annotate(rank=SearchRank(F("search_vector"), query))
.filter(rank__gt=min_confidence) .filter(rank__gt=min_confidence)
.order_by("-rank") .order_by("-rank")

View file

@ -140,7 +140,7 @@ TEMPLATES = [
}, },
] ]
LOG_LEVEL = env("LOG_LEVEL", "INFO").upper() LOG_LEVEL = env("LOG_LEVEL", "DEBUG").upper()
# Override aspects of the default handler to our taste # Override aspects of the default handler to our taste
# See https://docs.djangoproject.com/en/3.2/topics/logging/#default-logging-configuration # See https://docs.djangoproject.com/en/3.2/topics/logging/#default-logging-configuration
# for a reference to the defaults we're overriding # for a reference to the defaults we're overriding

View file

@ -0,0 +1,25 @@
{% load i18n %}
{% load utilities %}
<form class="navbar-item column is-align-items-start pt-5" action="{% url 'user-shelves' user|username %}">
<div class="field has-addons">
<div class="control">
{% trans "Search for a book" as my_shelves_search_placeholder %}
<input aria-label="{{ my_shelves_search_placeholder }}" id="my-books-search" class="input" type="text" name="shelves_q" placeholder="{{ my_shelves_search_placeholder }}" value="{{ my_shelves_query }}" spellcheck="false">
</div>
<div class="control">
<button class="button" type="submit">
<span class="icon icon-search" title="{% trans 'Search' %}">
<span class="is-sr-only">{% trans "Search" %}</span>
</span>
</button>
</div>
<div class="control">
<button class="button" type="button" data-modal-open="barcode-scanner-modal">
<span class="icon icon-barcode" title="{% trans 'Scan Barcode' %}" id="tour-barcode">
<span class="is-sr-only">{% trans "Scan Barcode" %}</span>
</span>
</button>
</div>
</div>
</form>

View file

@ -123,6 +123,7 @@
</span> </span>
{% endif %} {% endif %}
{% endwith %} {% endwith %}
{% include 'shelf/search_my_books_form.html' with user=user query=query %}
</h2> </h2>
</div> </div>
{% if is_self and shelf.id %} {% if is_self and shelf.id %}

View file

@ -9,7 +9,7 @@ from django.test import TestCase
from django.test.client import RequestFactory from django.test.client import RequestFactory
from bookwyrm import models, views from bookwyrm import models, views
from bookwyrm.book_search import SearchResult from bookwyrm.book_search import SearchResult, search
from bookwyrm.settings import DOMAIN from bookwyrm.settings import DOMAIN
from bookwyrm.tests.validate_html import validate_html from bookwyrm.tests.validate_html import validate_html

View file

@ -51,7 +51,7 @@ class Search(View):
def api_book_search(request): def api_book_search(request):
"""Return books via API response""" """Return books via API response"""
query = request.GET.get("q") query = request.GET.get("q")
query = isbn_check(query) query = isbn_check_and_format(query)
min_confidence = request.GET.get("min_confidence", 0) min_confidence = request.GET.get("min_confidence", 0)
# only return local book results via json so we don't cascade # only return local book results via json so we don't cascade
book_results = search(query, min_confidence=min_confidence) book_results = search(query, min_confidence=min_confidence)
@ -64,7 +64,7 @@ def book_search(request):
"""the real business is elsewhere""" """the real business is elsewhere"""
query = request.GET.get("q") query = request.GET.get("q")
# check if query is isbn # check if query is isbn
query = isbn_check(query) query = isbn_check_and_format(query)
min_confidence = request.GET.get("min_confidence", 0) min_confidence = request.GET.get("min_confidence", 0)
search_remote = request.GET.get("remote", False) and request.user.is_authenticated search_remote = request.GET.get("remote", False) and request.user.is_authenticated
@ -159,7 +159,7 @@ def list_search(request):
return TemplateResponse(request, "search/list.html", data) 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""" """isbn10 or isbn13 check, if so remove separators"""
if query: if query:
su_num = re.sub(r"(?<=\d)\D(?=\d|[xX])", "", query) su_num = re.sub(r"(?<=\d)\D(?=\d|[xX])", "", query)

View file

@ -1,7 +1,7 @@
""" shelf views """ """ shelf views """
from collections import namedtuple from collections import namedtuple
from django.db.models import OuterRef, Subquery, F, Max from django.db.models import OuterRef, Subquery, F, Max, QuerySet
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.http import HttpResponseBadRequest from django.http import HttpResponseBadRequest
@ -15,6 +15,10 @@ from bookwyrm import forms, models
from bookwyrm.activitypub import ActivitypubResponse from bookwyrm.activitypub import ActivitypubResponse
from bookwyrm.settings import PAGE_LENGTH from bookwyrm.settings import PAGE_LENGTH
from bookwyrm.views.helpers import is_api_request, get_user_from_username from bookwyrm.views.helpers import is_api_request, get_user_from_username
from bookwyrm.book_search import search
import logging
logger = logging.getLogger(__name__)
# pylint: disable=no-self-use # pylint: disable=no-self-use
@ -32,6 +36,8 @@ class Shelf(View):
else: else:
shelves = models.Shelf.privacy_filter(request.user).filter(user=user).all() shelves = models.Shelf.privacy_filter(request.user).filter(user=user).all()
shelves_search_query = request.GET.get("shelves_q")
# get the shelf and make sure the logged in user should be able to see it # get the shelf and make sure the logged in user should be able to see it
if shelf_identifier: if shelf_identifier:
shelf = get_object_or_404(user.shelf_set, identifier=shelf_identifier) shelf = get_object_or_404(user.shelf_set, identifier=shelf_identifier)
@ -42,14 +48,17 @@ class Shelf(View):
FakeShelf = namedtuple( FakeShelf = namedtuple(
"Shelf", ("identifier", "name", "user", "books", "privacy") "Shelf", ("identifier", "name", "user", "books", "privacy")
) )
books = ( if shelves_search_query:
models.Edition.viewer_aware_objects(request.user) logger.debug("AAAAAAAAAAAA")
.filter( all_books = models.Edition.viewer_aware_objects(request.user).filter(
# privacy is ensured because the shelves are already filtered above # privacy is ensured because the shelves are already filtered above
shelfbook__shelf__in=shelves shelfbook__shelf__in=shelves
) ).distinct()
.distinct() books = search(shelves_search_query, books=all_books)
) else:
logger.debug("BBBBBBBBB")
books = shelf.books
shelf = FakeShelf("all", _("All books"), user, books, "public") shelf = FakeShelf("all", _("All books"), user, books, "public")
if is_api_request(request) and shelf_identifier: if is_api_request(request) and shelf_identifier:
@ -103,6 +112,8 @@ class Shelf(View):
"page_range": paginated.get_elided_page_range( "page_range": paginated.get_elided_page_range(
page.number, on_each_side=2, on_ends=1 page.number, on_each_side=2, on_ends=1
), ),
"has_shelves_query": bool(shelves_search_query),
"shelves_search_query": shelves_search_query
} }
return TemplateResponse(request, "shelf/shelf.html", data) return TemplateResponse(request, "shelf/shelf.html", data)

View file

@ -21,6 +21,8 @@ services:
- pgdata:/var/lib/postgresql/data - pgdata:/var/lib/postgresql/data
networks: networks:
- main - main
ports:
- "5432:5432"
web: web:
build: . build: .
env_file: .env env_file: .env