diff --git a/bookwyrm/tests/test_views.py b/bookwyrm/tests/test_views.py index b060de9f6..66721fd4b 100644 --- a/bookwyrm/tests/test_views.py +++ b/bookwyrm/tests/test_views.py @@ -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'}) diff --git a/bookwyrm/tests/views/test_search.py b/bookwyrm/tests/views/test_search.py new file mode 100644 index 000000000..3f1d78503 --- /dev/null +++ b/bookwyrm/tests/views/test_search.py @@ -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) diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index d7efa0fbe..56dac60ac 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -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[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'^(?Phome|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), diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index 609fbfa03..778badeb8 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -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 diff --git a/bookwyrm/views/search.py b/bookwyrm/views/search.py new file mode 100644 index 000000000..8066777a0 --- /dev/null +++ b/bookwyrm/views/search.py @@ -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) diff --git a/bookwyrm/vviews.py b/bookwyrm/vviews.py index a9d758791..d849970ad 100644 --- a/bookwyrm/vviews.py +++ b/bookwyrm/vviews.py @@ -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)