diff --git a/src/tests/test_watson/admin.py b/src/tests/test_watson/admin.py index c7784bc..65ff016 100644 --- a/src/tests/test_watson/admin.py +++ b/src/tests/test_watson/admin.py @@ -1,14 +1,14 @@ from django.contrib import admin -import watson +from watson.admin import SearchAdmin from test_watson.models import WatsonTestModel1 -class WatsonTestModel1Admin(watson.SearchAdmin): +class WatsonTestModel1Admin(SearchAdmin): search_fields = ("title", "description", "content",) - + list_display = ("title",) - - + + admin.site.register(WatsonTestModel1, WatsonTestModel1Admin) diff --git a/src/tests/test_watson/tests.py b/src/tests/test_watson/tests.py index f941a4d..849797c 100644 --- a/src/tests/test_watson/tests.py +++ b/src/tests/test_watson/tests.py @@ -22,8 +22,7 @@ from django.contrib.auth.models import User from django import template from django.utils.encoding import force_text -import watson -from watson.registration import RegistrationError, get_backend, SearchEngine +from watson import search as watson from watson.models import SearchEntry from test_watson.models import WatsonTestModel1, WatsonTestModel2 @@ -31,30 +30,30 @@ from test_watson import admin # Force early registration of all admin models. class RegistrationTest(TestCase): - + def testRegistration(self): # Register the model and test. watson.register(WatsonTestModel1) self.assertTrue(watson.is_registered(WatsonTestModel1)) - self.assertRaises(RegistrationError, lambda: watson.register(WatsonTestModel1)) + self.assertRaises(watson.RegistrationError, lambda: watson.register(WatsonTestModel1)) self.assertTrue(WatsonTestModel1 in watson.get_registered_models()) self.assertTrue(isinstance(watson.get_adapter(WatsonTestModel1), watson.SearchAdapter)) # Unregister the model and text. watson.unregister(WatsonTestModel1) self.assertFalse(watson.is_registered(WatsonTestModel1)) - self.assertRaises(RegistrationError, lambda: watson.unregister(WatsonTestModel1)) + self.assertRaises(watson.RegistrationError, lambda: watson.unregister(WatsonTestModel1)) self.assertTrue(WatsonTestModel1 not in watson.get_registered_models()) - self.assertRaises(RegistrationError, lambda: isinstance(watson.get_adapter(WatsonTestModel1))) + self.assertRaises(watson.RegistrationError, lambda: isinstance(watson.get_adapter(WatsonTestModel1))) -complex_registration_search_engine = SearchEngine("restricted") +complex_registration_search_engine = watson.SearchEngine("restricted") class InstallUninstallTestBase(TestCase): - - @skipUnless(get_backend().requires_installation, "search backend does not require installation") + + @skipUnless(watson.get_backend().requires_installation, "search backend does not require installation") def testUninstallAndInstall(self): - backend = get_backend() + backend = watson.get_backend() call_command("uninstallwatson", verbosity=0) self.assertFalse(backend.is_installed()) call_command("installwatson", verbosity=0) @@ -64,7 +63,7 @@ class InstallUninstallTestBase(TestCase): class SearchTestBase(TestCase): model1 = WatsonTestModel1 - + model2 = WatsonTestModel2 def setUp(self): @@ -168,14 +167,14 @@ class InternalsTest(SearchTestBase): # Delete a model and make sure that the search results match. self.test11.delete() self.assertEqual(watson.search("fooo").count(), 0) - + def testSearchIndexUpdateDeferredByContext(self): with watson.update_index(): self.test11.title = "fooo" self.test11.save() self.assertEqual(watson.search("fooo").count(), 0) self.assertEqual(watson.search("fooo").count(), 1) - + def testSearchIndexUpdateAbandonedOnError(self): try: with watson.update_index(): @@ -186,14 +185,14 @@ class InternalsTest(SearchTestBase): pass # Test a search that should get not model. self.assertEqual(watson.search("fooo").count(), 0) - + def testSkipSearchIndexUpdate(self): with watson.skip_index_update(): self.test11.title = "fooo" self.test11.save() # Test a search that should get not model. self.assertEqual(watson.search("fooo").count(), 0) - + def testNestedSkipInUpdateContext(self): with watson.update_index(): self.test21.title = "baar" @@ -228,12 +227,12 @@ class InternalsTest(SearchTestBase): call_command("buildwatson", verbosity=0) # Make sure that we have four again (including duplicates). self.assertEqual(search_entries.all().count(), 4) - + def testEmptyFilterGivesAllResults(self): for model in (WatsonTestModel1, WatsonTestModel2): self.assertEqual(watson.filter(model, "").count(), 2) self.assertEqual(watson.filter(model, " ").count(), 2) - + def testFilter(self): for model in (WatsonTestModel1, WatsonTestModel2): # Test can find all. @@ -246,18 +245,18 @@ class InternalsTest(SearchTestBase): obj = watson.filter(WatsonTestModel1.objects.filter(title__icontains="TITLE"), "INSTANCE12").get() self.assertTrue(isinstance(obj, WatsonTestModel1)) self.assertEqual(obj.title, "title model1 instance12") - - @skipUnless(get_backend().supports_prefix_matching, "Search backend does not support prefix matching.") + + @skipUnless(watson.get_backend().supports_prefix_matching, "Search backend does not support prefix matching.") def testPrefixFilter(self): self.assertEqual(watson.filter(WatsonTestModel1, "INSTAN").count(), 2) - - + + class SearchTest(SearchTestBase): - + def emptySearchTextGivesNoResults(self): self.assertEqual(watson.search("").count(), 0) - self.assertEqual(watson.search(" ").count(), 0) - + self.assertEqual(watson.search(" ").count(), 0) + def testMultiTableSearch(self): # Test a search that should get all models. self.assertEqual(watson.search("TITLE").count(), 4) @@ -302,11 +301,11 @@ class SearchTest(SearchTestBase): description = "description model1 instance13", ) self.assertTrue(watson.search("'content").exists()) # Some database engines ignore leading apostrophes, some count them. - - @skipUnless(get_backend().supports_prefix_matching, "Search backend does not support prefix matching.") + + @skipUnless(watson.get_backend().supports_prefix_matching, "Search backend does not support prefix matching.") def testMultiTablePrefixSearch(self): self.assertEqual(watson.search("DESCR").count(), 4) - + def testLimitedModelList(self): # Test a search that should get all models. self.assertEqual(watson.search("TITLE", models=(WatsonTestModel1, WatsonTestModel2)).count(), 4) @@ -325,7 +324,7 @@ class SearchTest(SearchTestBase): self.assertEqual(watson.search("MODEL2", models=(WatsonTestModel1,)).count(), 0) self.assertEqual(watson.search("INSTANCE21", models=(WatsonTestModel1,)).count(), 0) self.assertEqual(watson.search("INSTANCE11", models=(WatsonTestModel2,)).count(), 0) - + def testExcludedModelList(self): # Test a search that should get all models. self.assertEqual(watson.search("TITLE", exclude=()).count(), 4) @@ -371,7 +370,7 @@ class SearchTest(SearchTestBase): self.assertEqual(watson.search("INSTANCE21", models=(WatsonTestModel2.objects.filter( title__icontains = "MODEL1", ),)).count(), 0) - + def testExcludedModelQuerySet(self): # Test a search that should get all models. self.assertEqual(watson.search("TITLE", exclude=(WatsonTestModel1.objects.filter(title__icontains="FOOO"), WatsonTestModel2.objects.filter(title__icontains="FOOO"),)).count(), 4) @@ -398,7 +397,7 @@ class SearchTest(SearchTestBase): self.assertEqual(watson.search("INSTANCE21", exclude=(WatsonTestModel2.objects.filter( title__icontains = "MODEL2", ),)).count(), 0) - + def testKitchenSink(self): """For sanity, let's just test everything together in one giant search of doom!""" self.assertEqual(watson.search( @@ -412,14 +411,14 @@ class SearchTest(SearchTestBase): WatsonTestModel2.objects.filter(title__icontains="MODEL1"), ) ).get().title, "title model1 instance11") - - + + class LiveFilterSearchTest(SearchTest): - + model1 = WatsonTestModel1.objects.filter(is_published=True) - + model2 = WatsonTestModel2.objects.filter(is_published=True) - + def testUnpublishedModelsNotFound(self): # Make sure that there are four to find! self.assertEqual(watson.search("tItle Content Description").count(), 4) @@ -430,15 +429,15 @@ class LiveFilterSearchTest(SearchTest): self.test21.save() # This should return 4, but two of them are unpublished. self.assertEqual(watson.search("tItle Content Description").count(), 2) - + def testCanOverridePublication(self): # Unpublish two objects. self.test11.is_published = False self.test11.save() # This should still return 4, since we're overriding the publication. self.assertEqual(watson.search("tItle Content Description", models=(WatsonTestModel2, WatsonTestModel1._base_manager.all(),)).count(), 4) - - + + class RankingTest(SearchTestBase): def setUp(self): @@ -450,24 +449,24 @@ class RankingTest(SearchTestBase): def testRankingParamPresentOnSearch(self): self.assertGreater(watson.search("TITLE")[0].watson_rank, 0) - + def testRankingParamPresentOnFilter(self): self.assertGreater(watson.filter(WatsonTestModel1, "TITLE")[0].watson_rank, 0) - + def testRankingParamAbsentOnSearch(self): self.assertRaises(AttributeError, lambda: watson.search("TITLE", ranking=False)[0].watson_rank) - + def testRankingParamAbsentOnFilter(self): self.assertRaises(AttributeError, lambda: watson.filter(WatsonTestModel1, "TITLE", ranking=False)[0].watson_rank) - - @skipUnless(get_backend().supports_ranking, "search backend does not support ranking") + + @skipUnless(watson.get_backend().supports_ranking, "search backend does not support ranking") def testRankingWithSearch(self): self.assertEqual( [entry.title for entry in watson.search("FOOO")], ["title model1 instance11 fooo baar fooo", "title model1 instance12"] ) - - @skipUnless(get_backend().supports_ranking, "search backend does not support ranking") + + @skipUnless(watson.get_backend().supports_ranking, "search backend does not support ranking") def testRankingWithFilter(self): self.assertEqual( [entry.title for entry in watson.filter(WatsonTestModel1, "FOOO")], @@ -479,15 +478,15 @@ class ComplexRegistrationTest(SearchTestBase): def testMetaStored(self): self.assertEqual(complex_registration_search_engine.search("instance11")[0].meta["is_published"], True) - + def testMetaNotStored(self): self.assertRaises(KeyError, lambda: complex_registration_search_engine.search("instance21")[0].meta["is_published"]) - + def testFieldsExcludedOnSearch(self): self.assertEqual(complex_registration_search_engine.search("TITLE").count(), 4) self.assertEqual(complex_registration_search_engine.search("CONTENT").count(), 0) self.assertEqual(complex_registration_search_engine.search("DESCRIPTION").count(), 0) - + def testFieldsExcludedOnFilter(self): self.assertEqual(complex_registration_search_engine.filter(WatsonTestModel1, "TITLE").count(), 2) self.assertEqual(complex_registration_search_engine.filter(WatsonTestModel1, "CONTENT").count(), 0) @@ -500,7 +499,7 @@ class ComplexRegistrationTest(SearchTestBase): class AdminIntegrationTest(SearchTestBase): urls = "test_watson.urls" - + def setUp(self): super(AdminIntegrationTest, self).setUp() self.user = User( @@ -510,7 +509,7 @@ class AdminIntegrationTest(SearchTestBase): ) self.user.set_password("bar") self.user.save() - + @skipUnless("django.contrib.admin" in settings.INSTALLED_APPS, "Django admin site not installed") def testAdminIntegration(self): # Log the user in. @@ -531,17 +530,17 @@ class AdminIntegrationTest(SearchTestBase): response = self.client.get("/admin/test_watson/watsontestmodel1/?q=instance11") self.assertContains(response, "instance11") self.assertNotContains(response, "instance12") - + def tearDown(self): super(AdminIntegrationTest, self).tearDown() self.user.delete() del self.user - - + + class SiteSearchTest(SearchTestBase): urls = "test_watson.urls" - + def testSiteSearch(self): # Test a search than should find everything. response = self.client.get("/simple/?q=title") @@ -562,7 +561,7 @@ class SiteSearchTest(SearchTestBase): self.assertNotContains(response, "instance12") self.assertNotContains(response, "instance21") self.assertNotContains(response, "instance22") - + def testSiteSearchJSON(self): # Test a search that should find everything. response = self.client.get("/simple/json/?q=title") @@ -573,7 +572,7 @@ class SiteSearchTest(SearchTestBase): self.assertTrue("title model1 instance12" in results) self.assertTrue("title model2 instance21" in results) self.assertTrue("title model2 instance22" in results) - + def testSiteSearchCustom(self): # Test a search than should find everything. response = self.client.get("/custom/?fooo=title") @@ -605,7 +604,7 @@ class SiteSearchTest(SearchTestBase): # Test a search that should find nothing. response = self.client.get("/custom/?q=fooo") self.assertRedirects(response, "/simple/") - + def testSiteSearchCustomJSON(self): # Test a search that should find everything. response = self.client.get("/custom/json/?fooo=title&page=last") diff --git a/src/watson/__init__.py b/src/watson/__init__.py index 56d397d..bd3ef74 100644 --- a/src/watson/__init__.py +++ b/src/watson/__init__.py @@ -5,26 +5,3 @@ Developed by Dave Hall. """ - -from __future__ import unicode_literals - -from watson.admin import SearchAdmin -from watson.registration import SearchAdapter, default_search_engine, search_context_manager - - -# The main search methods. -search = default_search_engine.search -filter = default_search_engine.filter - - -# Easy registration. -register = default_search_engine.register -unregister = default_search_engine.unregister -is_registered = default_search_engine.is_registered -get_registered_models = default_search_engine.get_registered_models -get_adapter = default_search_engine.get_adapter - - -# Easy context management. -update_index = search_context_manager.update_index -skip_index_update = search_context_manager.skip_index_update diff --git a/src/watson/admin.py b/src/watson/admin.py index 0235c8a..fee50ee 100644 --- a/src/watson/admin.py +++ b/src/watson/admin.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals from django.contrib import admin from django.contrib.admin.views.main import ChangeList -from watson.registration import SearchEngine, SearchAdapter +from watson.search import SearchEngine, SearchAdapter admin_search_engine = SearchEngine("admin") diff --git a/src/watson/management/commands/buildwatson.py b/src/watson/management/commands/buildwatson.py index 32cbe5a..33b0238 100644 --- a/src/watson/management/commands/buildwatson.py +++ b/src/watson/management/commands/buildwatson.py @@ -14,7 +14,7 @@ from django.utils.translation import activate from django.conf import settings -from watson.registration import SearchEngine, _bulk_save_search_entries +from watson.search import SearchEngine, _bulk_save_search_entries from watson.models import SearchEntry diff --git a/src/watson/management/commands/installwatson.py b/src/watson/management/commands/installwatson.py index 96c3568..f206728 100644 --- a/src/watson/management/commands/installwatson.py +++ b/src/watson/management/commands/installwatson.py @@ -4,13 +4,13 @@ from __future__ import unicode_literals from django.core.management.base import NoArgsCommand -from watson.registration import get_backend +from watson.search import get_backend class Command(NoArgsCommand): help = "Creates the database indices needed by django-watson." - + def handle_noargs(self, **options): """Runs the management command.""" verbosity = int(options.get("verbosity", 1)) diff --git a/src/watson/management/commands/listwatson.py b/src/watson/management/commands/listwatson.py index 0311132..6292bee 100644 --- a/src/watson/management/commands/listwatson.py +++ b/src/watson/management/commands/listwatson.py @@ -1,16 +1,16 @@ """Exposed the watson.get_registered_models() function as management command for debugging purpose. """ from django.core.management.base import NoArgsCommand -import watson +from watson import search as watson class Command(NoArgsCommand): help = "List all registed models by django-watson." - + def handle_noargs(self, **options): """Runs the management command.""" self.stdout.write("The following models are registed for the django-watson search engine:\n") for mdl in watson.get_registered_models(): self.stdout.write("- %s\n" % mdl.__name__) - - + + diff --git a/src/watson/management/commands/uninstallwatson.py b/src/watson/management/commands/uninstallwatson.py index b4bbf0d..a537e24 100644 --- a/src/watson/management/commands/uninstallwatson.py +++ b/src/watson/management/commands/uninstallwatson.py @@ -4,13 +4,13 @@ from __future__ import unicode_literals from django.core.management.base import NoArgsCommand -from watson.registration import get_backend +from watson.search import get_backend class Command(NoArgsCommand): help = "Destroys the database indices needed by django-watson." - + def handle_noargs(self, **options): """Runs the management command.""" verbosity = int(options.get("verbosity", 1)) diff --git a/src/watson/middleware.py b/src/watson/middleware.py index 8ba8e18..e624762 100644 --- a/src/watson/middleware.py +++ b/src/watson/middleware.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals from django.core.exceptions import ImproperlyConfigured -from watson.registration import search_context_manager +from watson.search import search_context_manager WATSON_MIDDLEWARE_FLAG = "watson.search_context_middleware_active" diff --git a/src/watson/models.py b/src/watson/models.py index 7e5fdc3..108993c 100644 --- a/src/watson/models.py +++ b/src/watson/models.py @@ -73,7 +73,7 @@ class SearchEntry(models.Model): meta_encoded = models.TextField() def _deserialize_meta(self): - from watson.registration import SearchEngine + from watson.search import SearchEngine engine = SearchEngine._created_engines[self.engine_slug] model = ContentType.objects.get_for_id(self.content_type_id).model_class() adapter = engine.get_adapter(model) diff --git a/src/watson/registration.py b/src/watson/search.py similarity index 97% rename from src/watson/registration.py rename to src/watson/search.py index 9dfb9b8..7b3c901 100644 --- a/src/watson/registration.py +++ b/src/watson/search.py @@ -628,3 +628,21 @@ def get_backend(backend_name=None): backend = backend_cls() _backends_cache[backend_name] = backend return backend + + +# The main search methods. +search = default_search_engine.search +filter = default_search_engine.filter + + +# Easy registration. +register = default_search_engine.register +unregister = default_search_engine.unregister +is_registered = default_search_engine.is_registered +get_registered_models = default_search_engine.get_registered_models +get_adapter = default_search_engine.get_adapter + + +# Easy context management. +update_index = search_context_manager.update_index +skip_index_update = search_context_manager.skip_index_update diff --git a/src/watson/views.py b/src/watson/views.py index 0cdc7ef..1cf5e63 100644 --- a/src/watson/views.py +++ b/src/watson/views.py @@ -10,57 +10,57 @@ from django.utils import six from django.views import generic from django.views.generic.list import BaseListView -import watson +from watson import search as watson class SearchMixin(object): - + """Base mixin for search views.""" - + context_object_name = "search_results" - + query_param = "q" - + def get_query_param(self): """Returns the query parameter to use in the request GET dictionary.""" return self.query_param - + models = () - + def get_models(self): """Returns the models to use in the query.""" - return self.models - + return self.models + exclude = () - + def get_exclude(self): """Returns the models to exclude from the query.""" return self.exclude - + def get_queryset(self): """Returns the initial queryset.""" return watson.search(self.query, models=self.get_models(), exclude=self.get_exclude()) - + def get_query(self, request): """Parses the query from the request.""" return request.GET.get(self.get_query_param(), "").strip() - + empty_query_redirect = None - + def get_empty_query_redirect(self): """Returns the URL to redirect an empty query to, or None.""" return self.empty_query_redirect - + extra_context = {} - + def get_extra_context(self): """ Returns any extra context variables. - + Required for backwards compatibility with old function-based views. """ return self.extra_context - + def get_context_data(self, **kwargs): """Generates context variables.""" context = super(SearchMixin, self).get_context_data(**kwargs) @@ -71,7 +71,7 @@ class SearchMixin(object): value = value() context[key] = value return context - + def get(self, request, *args, **kwargs): """Performs a GET request.""" self.query = self.get_query(request) @@ -83,16 +83,16 @@ class SearchMixin(object): class SearchView(SearchMixin, generic.ListView): - + """View that performs a search and returns the search results.""" - + template_name = "watson/search_results.html" - - + + class SearchApiView(SearchMixin, BaseListView): - + """A JSON-based search API.""" - + def render_to_response(self, context, **response_kwargs): """Renders the search results to the response.""" content = json.dumps({ @@ -117,8 +117,8 @@ class SearchApiView(SearchMixin, BaseListView): def search(request, **kwargs): """Renders a page of search results.""" return SearchView.as_view(**kwargs)(request) - - + + def search_json(request, **kwargs): """Renders a JSON representation of matching search entries.""" return SearchApiView.as_view(**kwargs)(request)