forked from mirrors/bookwyrm
Search class views
This commit is contained in:
parent
beeeaaaf39
commit
b6bdfab943
6 changed files with 169 additions and 181 deletions
|
@ -1,17 +1,12 @@
|
|||
''' test for app action functionality '''
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.http import JsonResponse
|
||||
from django.template.response import TemplateResponse
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
from bookwyrm import models
|
||||
from bookwyrm import vviews as views
|
||||
from bookwyrm.activitypub import ActivitypubResponse
|
||||
from bookwyrm.connectors import abstract_connector
|
||||
from bookwyrm.settings import DOMAIN, USER_AGENT
|
||||
from bookwyrm.settings import USER_AGENT
|
||||
|
||||
|
||||
# pylint: disable=too-many-public-methods
|
||||
|
@ -142,128 +137,6 @@ class Views(TestCase):
|
|||
self.assertEqual(statuses[0], rat_mention)
|
||||
|
||||
|
||||
def test_search_json_response(self):
|
||||
''' searches local data only and returns book data in json format '''
|
||||
# we need a connector for this, sorry
|
||||
request = self.factory.get('', {'q': 'Test Book'})
|
||||
with patch('bookwyrm.views.is_api_request') as is_api:
|
||||
is_api.return_value = True
|
||||
response = views.search(request)
|
||||
self.assertIsInstance(response, JsonResponse)
|
||||
|
||||
data = json.loads(response.content)
|
||||
self.assertEqual(len(data), 1)
|
||||
self.assertEqual(data[0]['title'], 'Test Book')
|
||||
self.assertEqual(
|
||||
data[0]['key'], 'https://%s/book/%d' % (DOMAIN, self.book.id))
|
||||
|
||||
|
||||
def test_search_html_response(self):
|
||||
''' searches remote connectors '''
|
||||
class TestConnector(abstract_connector.AbstractMinimalConnector):
|
||||
''' nothing added here '''
|
||||
def format_search_result(self, search_result):
|
||||
pass
|
||||
def get_or_create_book(self, remote_id):
|
||||
pass
|
||||
def parse_search_data(self, data):
|
||||
pass
|
||||
models.Connector.objects.create(
|
||||
identifier='example.com',
|
||||
connector_file='openlibrary',
|
||||
base_url='https://example.com',
|
||||
books_url='https://example.com/books',
|
||||
covers_url='https://example.com/covers',
|
||||
search_url='https://example.com/search?q=',
|
||||
)
|
||||
connector = TestConnector('example.com')
|
||||
|
||||
search_result = abstract_connector.SearchResult(
|
||||
key='http://www.example.com/book/1',
|
||||
title='Gideon the Ninth',
|
||||
author='Tamsyn Muir',
|
||||
year='2019',
|
||||
connector=connector
|
||||
)
|
||||
|
||||
request = self.factory.get('', {'q': 'Test Book'})
|
||||
with patch('bookwyrm.views.is_api_request') as is_api:
|
||||
is_api.return_value = False
|
||||
with patch(
|
||||
'bookwyrm.connectors.connector_manager.search') as manager:
|
||||
manager.return_value = [search_result]
|
||||
response = views.search(request)
|
||||
self.assertIsInstance(response, TemplateResponse)
|
||||
self.assertEqual(response.template_name, 'search_results.html')
|
||||
self.assertEqual(
|
||||
response.context_data['book_results'][0].title, 'Gideon the Ninth')
|
||||
|
||||
|
||||
def test_search_html_response_users(self):
|
||||
''' searches remote connectors '''
|
||||
request = self.factory.get('', {'q': 'mouse'})
|
||||
with patch('bookwyrm.views.is_api_request') as is_api:
|
||||
is_api.return_value = False
|
||||
with patch('bookwyrm.connectors.connector_manager.search'):
|
||||
response = views.search(request)
|
||||
self.assertIsInstance(response, TemplateResponse)
|
||||
self.assertEqual(response.template_name, 'search_results.html')
|
||||
self.assertEqual(
|
||||
response.context_data['user_results'][0], self.local_user)
|
||||
|
||||
|
||||
def test_tag_page(self):
|
||||
''' there are so many views, this just makes sure it LOADS '''
|
||||
tag = models.Tag.objects.create(name='hi there')
|
||||
models.UserTag.objects.create(
|
||||
tag=tag, user=self.local_user, book=self.book)
|
||||
request = self.factory.get('')
|
||||
with patch('bookwyrm.views.is_api_request') as is_api:
|
||||
is_api.return_value = False
|
||||
result = views.tag_page(request, tag.identifier)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
self.assertEqual(result.template_name, 'tag.html')
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
request = self.factory.get('')
|
||||
with patch('bookwyrm.views.is_api_request') as is_api:
|
||||
is_api.return_value = True
|
||||
result = views.tag_page(request, tag.identifier)
|
||||
self.assertIsInstance(result, ActivitypubResponse)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
|
||||
def test_shelf_page(self):
|
||||
''' there are so many views, this just makes sure it LOADS '''
|
||||
shelf = self.local_user.shelf_set.first()
|
||||
request = self.factory.get('')
|
||||
request.user = self.local_user
|
||||
with patch('bookwyrm.views.is_api_request') as is_api:
|
||||
is_api.return_value = False
|
||||
result = views.shelf_page(
|
||||
request, self.local_user.username, shelf.identifier)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
self.assertEqual(result.template_name, 'shelf.html')
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
with patch('bookwyrm.views.is_api_request') as is_api:
|
||||
is_api.return_value = True
|
||||
result = views.shelf_page(
|
||||
request, self.local_user.username, shelf.identifier)
|
||||
self.assertIsInstance(result, ActivitypubResponse)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
|
||||
request = self.factory.get('/?page=1')
|
||||
request.user = self.local_user
|
||||
with patch('bookwyrm.views.is_api_request') as is_api:
|
||||
is_api.return_value = True
|
||||
result = views.shelf_page(
|
||||
request, self.local_user.username, shelf.identifier)
|
||||
self.assertIsInstance(result, ActivitypubResponse)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
|
||||
def test_is_bookwyrm_request(self):
|
||||
''' checks if a request came from a bookwyrm instance '''
|
||||
request = self.factory.get('', {'q': 'Test Book'})
|
||||
|
|
108
bookwyrm/tests/views/test_search.py
Normal file
108
bookwyrm/tests/views/test_search.py
Normal file
|
@ -0,0 +1,108 @@
|
|||
''' test for app action functionality '''
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.http import JsonResponse
|
||||
from django.template.response import TemplateResponse
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
from bookwyrm import models, views
|
||||
from bookwyrm.connectors import abstract_connector
|
||||
from bookwyrm.settings import DOMAIN
|
||||
|
||||
|
||||
class ShelfViews(TestCase):
|
||||
''' tag views'''
|
||||
def setUp(self):
|
||||
''' we need basic test data and mocks '''
|
||||
self.factory = RequestFactory()
|
||||
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='Test Book',
|
||||
remote_id='https://example.com/book/1',
|
||||
parent_work=self.work
|
||||
)
|
||||
models.Connector.objects.create(
|
||||
identifier='self',
|
||||
connector_file='self_connector',
|
||||
local=True
|
||||
)
|
||||
|
||||
|
||||
def test_search_json_response(self):
|
||||
''' searches local data only and returns book data in json format '''
|
||||
view = views.Search.as_view()
|
||||
# we need a connector for this, sorry
|
||||
request = self.factory.get('', {'q': 'Test Book'})
|
||||
with patch('bookwyrm.views.search.is_api_request') as is_api:
|
||||
is_api.return_value = True
|
||||
response = view(request)
|
||||
self.assertIsInstance(response, JsonResponse)
|
||||
|
||||
data = json.loads(response.content)
|
||||
self.assertEqual(len(data), 1)
|
||||
self.assertEqual(data[0]['title'], 'Test Book')
|
||||
self.assertEqual(
|
||||
data[0]['key'], 'https://%s/book/%d' % (DOMAIN, self.book.id))
|
||||
|
||||
|
||||
def test_search_html_response(self):
|
||||
''' searches remote connectors '''
|
||||
view = views.Search.as_view()
|
||||
class TestConnector(abstract_connector.AbstractMinimalConnector):
|
||||
''' nothing added here '''
|
||||
def format_search_result(self, search_result):
|
||||
pass
|
||||
def get_or_create_book(self, remote_id):
|
||||
pass
|
||||
def parse_search_data(self, data):
|
||||
pass
|
||||
models.Connector.objects.create(
|
||||
identifier='example.com',
|
||||
connector_file='openlibrary',
|
||||
base_url='https://example.com',
|
||||
books_url='https://example.com/books',
|
||||
covers_url='https://example.com/covers',
|
||||
search_url='https://example.com/search?q=',
|
||||
)
|
||||
connector = TestConnector('example.com')
|
||||
|
||||
search_result = abstract_connector.SearchResult(
|
||||
key='http://www.example.com/book/1',
|
||||
title='Gideon the Ninth',
|
||||
author='Tamsyn Muir',
|
||||
year='2019',
|
||||
connector=connector
|
||||
)
|
||||
|
||||
request = self.factory.get('', {'q': 'Test Book'})
|
||||
with patch('bookwyrm.views.search.is_api_request') as is_api:
|
||||
is_api.return_value = False
|
||||
with patch(
|
||||
'bookwyrm.connectors.connector_manager.search') as manager:
|
||||
manager.return_value = [search_result]
|
||||
response = view(request)
|
||||
self.assertIsInstance(response, TemplateResponse)
|
||||
self.assertEqual(response.template_name, 'search_results.html')
|
||||
self.assertEqual(
|
||||
response.context_data['book_results'][0].title, 'Gideon the Ninth')
|
||||
|
||||
|
||||
def test_search_html_response_users(self):
|
||||
''' searches remote connectors '''
|
||||
view = views.Search.as_view()
|
||||
request = self.factory.get('', {'q': 'mouse'})
|
||||
with patch('bookwyrm.views.search.is_api_request') as is_api:
|
||||
is_api.return_value = False
|
||||
with patch('bookwyrm.connectors.connector_manager.search'):
|
||||
response = view(request)
|
||||
self.assertIsInstance(response, TemplateResponse)
|
||||
self.assertEqual(response.template_name, 'search_results.html')
|
||||
self.assertEqual(
|
||||
response.context_data['user_results'][0], self.local_user)
|
|
@ -3,7 +3,7 @@ from django.conf.urls.static import static
|
|||
from django.contrib import admin
|
||||
from django.urls import path, re_path
|
||||
|
||||
from bookwyrm import incoming, outgoing, settings, vviews, views, wellknown
|
||||
from bookwyrm import incoming, outgoing, settings, views, wellknown
|
||||
from bookwyrm import view_actions as actions
|
||||
from bookwyrm.utils import regex
|
||||
|
||||
|
@ -49,11 +49,11 @@ urlpatterns = [
|
|||
views.PasswordReset.as_view()),
|
||||
re_path(r'^change-password/?$', views.ChangePassword),
|
||||
|
||||
#invites
|
||||
# invites
|
||||
re_path(r'^invite/?$', views.ManageInvites.as_view()),
|
||||
re_path(r'^invite/(?P<code>[A-Za-z0-9]+)/?$', views.Invite.as_view()),
|
||||
|
||||
#landing pages
|
||||
# landing pages
|
||||
re_path(r'^about/?$', views.About.as_view()),
|
||||
path('', views.Home.as_view()),
|
||||
re_path(r'^(?P<tab>home|local|federated)/?$', views.Feed.as_view()),
|
||||
|
@ -61,6 +61,9 @@ urlpatterns = [
|
|||
re_path(r'^notifications/?$', views.Notifications.as_view()),
|
||||
re_path(r'^direct-messages/?$', views.DirectMessage.as_view()),
|
||||
|
||||
# search
|
||||
re_path(r'^search/?$', views.Search.as_view()),
|
||||
|
||||
# imports
|
||||
re_path(r'^import/?$', views.Import.as_view()),
|
||||
re_path(r'^import/(\d+)/?$', views.ImportStatus.as_view()),
|
||||
|
@ -116,8 +119,6 @@ urlpatterns = [
|
|||
re_path(r'^shelve/?$', views.shelve),
|
||||
re_path(r'^unshelve/?$', views.unshelve),
|
||||
|
||||
re_path(r'^search/?$', vviews.search),
|
||||
|
||||
re_path(r'^edit-readthrough/?$', actions.edit_readthrough),
|
||||
re_path(r'^delete-readthrough/?$', actions.delete_readthrough),
|
||||
re_path(r'^create-readthrough/?$', actions.create_readthrough),
|
||||
|
|
|
@ -16,3 +16,4 @@ from .tag import Tag, AddTag, RemoveTag
|
|||
from .shelf import Shelf
|
||||
from .shelf import user_shelves_page, create_shelf, delete_shelf
|
||||
from .shelf import shelve, unshelve
|
||||
from .search import Search
|
||||
|
|
53
bookwyrm/views/search.py
Normal file
53
bookwyrm/views/search.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
''' search views'''
|
||||
import re
|
||||
|
||||
from django.contrib.postgres.search import TrigramSimilarity
|
||||
from django.db.models.functions import Greatest
|
||||
from django.http import JsonResponse
|
||||
from django.template.response import TemplateResponse
|
||||
from django.views import View
|
||||
|
||||
from bookwyrm import models
|
||||
from bookwyrm.connectors import connector_manager
|
||||
from bookwyrm.utils import regex
|
||||
from .helpers import is_api_request
|
||||
from .helpers import handle_remote_webfinger
|
||||
|
||||
|
||||
# pylint: disable= no-self-use
|
||||
class Search(View):
|
||||
''' search users or books '''
|
||||
def get(self, request):
|
||||
''' that search bar up top '''
|
||||
query = request.GET.get('q')
|
||||
min_confidence = request.GET.get('min_confidence', 0.1)
|
||||
|
||||
if is_api_request(request):
|
||||
# only return local book results via json so we don't cascade
|
||||
book_results = connector_manager.local_search(
|
||||
query, min_confidence=min_confidence)
|
||||
return JsonResponse([r.json() for r in book_results], safe=False)
|
||||
|
||||
# use webfinger for mastodon style account@domain.com username
|
||||
if re.match(r'\B%s' % regex.full_username, query):
|
||||
handle_remote_webfinger(query)
|
||||
|
||||
# do a local user search
|
||||
user_results = models.User.objects.annotate(
|
||||
similarity=Greatest(
|
||||
TrigramSimilarity('username', query),
|
||||
TrigramSimilarity('localname', query),
|
||||
)
|
||||
).filter(
|
||||
similarity__gt=0.5,
|
||||
).order_by('-similarity')[:10]
|
||||
|
||||
book_results = connector_manager.search(
|
||||
query, min_confidence=min_confidence)
|
||||
data = {
|
||||
'title': 'Search Results',
|
||||
'book_results': book_results,
|
||||
'user_results': user_results,
|
||||
'query': query,
|
||||
}
|
||||
return TemplateResponse(request, 'search_results.html', data)
|
|
@ -1,16 +1,5 @@
|
|||
''' views for pages you can go to in the application '''
|
||||
import re
|
||||
|
||||
from django.contrib.postgres.search import TrigramSimilarity
|
||||
from django.db.models.functions import Greatest
|
||||
from django.http import JsonResponse
|
||||
from django.template.response import TemplateResponse
|
||||
from django.views.decorators.http import require_GET
|
||||
|
||||
from bookwyrm import outgoing
|
||||
from bookwyrm import models
|
||||
from bookwyrm.connectors import connector_manager
|
||||
from bookwyrm.utils import regex
|
||||
|
||||
|
||||
def is_api_request(request):
|
||||
|
@ -28,40 +17,3 @@ def not_found_page(request, _):
|
|||
''' 404s '''
|
||||
return TemplateResponse(
|
||||
request, 'notfound.html', {'title': 'Not found'}, status=404)
|
||||
|
||||
|
||||
@require_GET
|
||||
def search(request):
|
||||
''' that search bar up top '''
|
||||
query = request.GET.get('q')
|
||||
min_confidence = request.GET.get('min_confidence', 0.1)
|
||||
|
||||
if is_api_request(request):
|
||||
# only return local book results via json so we don't cause a cascade
|
||||
book_results = connector_manager.local_search(
|
||||
query, min_confidence=min_confidence)
|
||||
return JsonResponse([r.json() for r in book_results], safe=False)
|
||||
|
||||
# use webfinger for mastodon style account@domain.com username
|
||||
if re.match(r'\B%s' % regex.full_username, query):
|
||||
outgoing.handle_remote_webfinger(query)
|
||||
|
||||
# do a local user search
|
||||
user_results = models.User.objects.annotate(
|
||||
similarity=Greatest(
|
||||
TrigramSimilarity('username', query),
|
||||
TrigramSimilarity('localname', query),
|
||||
)
|
||||
).filter(
|
||||
similarity__gt=0.5,
|
||||
).order_by('-similarity')[:10]
|
||||
|
||||
book_results = connector_manager.search(
|
||||
query, min_confidence=min_confidence)
|
||||
data = {
|
||||
'title': 'Search Results',
|
||||
'book_results': book_results,
|
||||
'user_results': user_results,
|
||||
'query': query,
|
||||
}
|
||||
return TemplateResponse(request, 'search_results.html', data)
|
||||
|
|
Loading…
Reference in a new issue