Merge pull request #1003 from bookwyrm-social/list-fixes

Improve list suggestion experience
This commit is contained in:
Mouse Reeve 2021-04-26 13:48:26 -07:00 committed by GitHub
commit d4dbfbe3c0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 10423 additions and 3179 deletions

View file

@ -67,10 +67,12 @@ def search(query, min_confidence=0.1):
return results return results
def local_search(query, min_confidence=0.1, raw=False): def local_search(query, min_confidence=0.1, raw=False, filters=None):
"""only look at local search results""" """only look at local search results"""
connector = load_connector(models.Connector.objects.get(local=True)) connector = load_connector(models.Connector.objects.get(local=True))
return connector.search(query, min_confidence=min_confidence, raw=raw) return connector.search(
query, min_confidence=min_confidence, raw=raw, filters=filters
)
def isbn_local_search(query, raw=False): def isbn_local_search(query, raw=False):

View file

@ -13,15 +13,16 @@ class Connector(AbstractConnector):
"""instantiate a connector""" """instantiate a connector"""
# pylint: disable=arguments-differ # pylint: disable=arguments-differ
def search(self, query, min_confidence=0.1, raw=False): def search(self, query, min_confidence=0.1, raw=False, filters=None):
"""search your local database""" """search your local database"""
filters = filters or []
if not query: if not query:
return [] return []
# first, try searching unqiue identifiers # first, try searching unqiue identifiers
results = search_identifiers(query) results = search_identifiers(query, *filters)
if not results: if not results:
# then try searching title/author # then try searching title/author
results = search_title_author(query, min_confidence) results = search_title_author(query, min_confidence, *filters)
search_results = [] search_results = []
for result in results: for result in results:
if raw: if raw:
@ -98,15 +99,15 @@ class Connector(AbstractConnector):
pass pass
def search_identifiers(query): def search_identifiers(query, *filters):
"""tries remote_id, isbn; defined as dedupe fields on the model""" """tries remote_id, isbn; defined as dedupe fields on the model"""
filters = [ or_filters = [
{f.name: query} {f.name: query}
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 = models.Edition.objects.filter(
reduce(operator.or_, (Q(**f) for f in filters)) *filters, reduce(operator.or_, (Q(**f) for f in or_filters))
).distinct() ).distinct()
# when there are multiple editions of the same work, pick the default. # when there are multiple editions of the same work, pick the default.
@ -114,7 +115,7 @@ def search_identifiers(query):
return results.filter(parent_work__default_edition__id=F("id")) or results return results.filter(parent_work__default_edition__id=F("id")) or results
def search_title_author(query, min_confidence): def search_title_author(query, min_confidence, *filters):
"""searches for title and author""" """searches for title and author"""
vector = ( vector = (
SearchVector("title", weight="A") SearchVector("title", weight="A")
@ -126,7 +127,7 @@ def search_title_author(query, min_confidence):
results = ( results = (
models.Edition.objects.annotate(search=vector) models.Edition.objects.annotate(search=vector)
.annotate(rank=SearchRank(vector, query)) .annotate(rank=SearchRank(vector, query))
.filter(rank__gt=min_confidence) .filter(*filters, rank__gt=min_confidence)
.order_by("-rank") .order_by("-rank")
) )

View file

@ -13,6 +13,16 @@
<div class="columns mt-3"> <div class="columns mt-3">
<section class="column is-three-quarters"> <section class="column is-three-quarters">
{% if request.GET.updated %}
<div class="notification is-primary">
{% if list.curation != "open" and request.user != list.user %}
{% trans "You successfully suggested a book for this list!" %}
{% else %}
{% trans "You successfully added a book to this list!" %}
{% endif %}
</div>
{% endif %}
{% if not items.object_list.exists %} {% if not items.object_list.exists %}
<p>{% trans "This list is currently empty" %}</p> <p>{% trans "This list is currently empty" %}</p>
{% else %} {% else %}
@ -116,7 +126,7 @@
</div> </div>
<div class="column"> <div class="column">
<p>{% include 'snippets/book_titleby.html' with book=book %}</p> <p>{% include 'snippets/book_titleby.html' with book=book %}</p>
<form name="add-book" method="post" action="{% url 'list-add-book' %}"> <form name="add-book" method="post" action="{% url 'list-add-book' %}{% if query %}?q={{ query }}{% endif %}">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="book" value="{{ book.id }}"> <input type="hidden" name="book" value="{{ book.id }}">
<input type="hidden" name="list" value="{{ list.id }}"> <input type="hidden" name="list" value="{{ list.id }}">

View file

@ -1,5 +1,6 @@
""" book list views""" """ book list views"""
from typing import Optional from typing import Optional
from urllib.parse import urlencode
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
@ -9,6 +10,7 @@ from django.db.models.functions import Coalesce
from django.http import HttpResponseNotFound, HttpResponseBadRequest, HttpResponse from django.http import HttpResponseNotFound, HttpResponseBadRequest, HttpResponse
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.urls import reverse
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views import View from django.views import View
from django.views.decorators.http import require_POST from django.views.decorators.http import require_POST
@ -135,7 +137,11 @@ class List(View):
if query and request.user.is_authenticated: if query and request.user.is_authenticated:
# search for books # search for books
suggestions = connector_manager.local_search(query, raw=True) suggestions = connector_manager.local_search(
query,
raw=True,
filters=[~Q(parent_work__editions__in=book_list.books.all())],
)
elif request.user.is_authenticated: elif request.user.is_authenticated:
# just suggest whatever books are nearby # just suggest whatever books are nearby
suggestions = request.user.shelfbook_set.filter( suggestions = request.user.shelfbook_set.filter(
@ -263,7 +269,10 @@ def add_book(request):
# if the book is already on the list, don't flip out # if the book is already on the list, don't flip out
pass pass
return redirect("list", book_list.id) path = reverse("list", args=[book_list.id])
params = request.GET.copy()
params["updated"] = True
return redirect("{:s}?{:s}".format(path, urlencode(params)))
@require_POST @require_POST

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff