diff --git a/.travis.yml b/.travis.yml
index 25fe7c4..265da0c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,14 +7,24 @@ cache:
- $HOME/.cache/pip
matrix:
fast_finish: true
+env:
+ matrix:
+ - WORKER=python
+ - WORKER=flake8
services:
- postgresql
- mysql
install:
- pip install tox
+ - pip install flake8
before_script:
- - mysql -e 'create database test_project'
- - psql -c 'create database test_project;' -U postgres
-script: tox
+ - |
+ if [[ $WORKER == python ]]; then
+ mysql -e 'create database test_project';
+ psql -c 'create database test_project;' -U postgres;
+ fi
+script:
+ - if [[ $WORKER == python ]]; then tox; fi
+ - if [[ $WORKER == flake8 ]]; then flake8 --jobs=2 . ; fi
notifications:
email: false
diff --git a/MANIFEST.in b/MANIFEST.in
index 3612c94..539cfd4 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,4 +1,4 @@
-include src/watson/templates/watson/*.html
-include src/watson/locale/*/LC_MESSAGES/django.*
+include watson/templates/watson/*.html
+include watson/locale/*/LC_MESSAGES/django.*
include LICENSE
include README.markdown
\ No newline at end of file
diff --git a/setup.py b/setup.py
index d12975e..601f487 100644
--- a/setup.py
+++ b/setup.py
@@ -3,15 +3,15 @@ from distutils.core import setup
from watson import __version__
setup(
- name = "django-watson",
- version = '.'.join(str(x) for x in __version__),
- description = "Full-text multi-table search application for Django. Easy to install and use, with good performance.",
- long_description = open(os.path.join(os.path.dirname(__file__), "README.markdown")).read(),
- author = "Dave Hall",
- author_email = "dave@etianen.com",
- url = "http://github.com/etianen/django-watson",
- zip_safe = False,
- packages = [
+ name="django-watson",
+ version='.'.join(str(x) for x in __version__),
+ description="Full-text multi-table search application for Django. Easy to install and use, with good performance.",
+ long_description=open(os.path.join(os.path.dirname(__file__), "README.markdown")).read(),
+ author="Dave Hall",
+ author_email="dave@etianen.com",
+ url="http://github.com/etianen/django-watson",
+ zip_safe=False,
+ packages=[
"watson",
"watson.management",
"watson.management.commands",
@@ -19,10 +19,7 @@ setup(
"watson.south_migrations",
"watson.templatetags",
],
- package_dir = {
- "": "src",
- },
- package_data = {
+ package_data={
"watson": [
"locale/*/LC_MESSAGES/django.*",
"templates/watson/*.html",
diff --git a/src/watson/__init__.py b/src/watson/__init__.py
deleted file mode 100644
index bd3ef74..0000000
--- a/src/watson/__init__.py
+++ /dev/null
@@ -1,7 +0,0 @@
-"""
-Multi-table search application for Django, using native database search engines.
-
-Developed by Dave Hall.
-
-
-"""
diff --git a/src/tests/__init__.py b/tests/__init__.py
similarity index 100%
rename from src/tests/__init__.py
rename to tests/__init__.py
diff --git a/src/tests/runtests.py b/tests/runtests.py
similarity index 95%
rename from src/tests/runtests.py
rename to tests/runtests.py
index de8ab0e..ab0d196 100755
--- a/src/tests/runtests.py
+++ b/tests/runtests.py
@@ -1,5 +1,6 @@
#!/usr/bin/env python
-import sys, os, os.path
+import os
+import sys
from optparse import OptionParser
AVAILABLE_DATABASES = {
@@ -97,9 +98,9 @@ def main():
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ['templates'],
'APP_DIRS': True,
- }],
-
+ }],
)
+
# Run Django setup (1.7+).
import django
try:
@@ -110,9 +111,9 @@ def main():
from django.test.utils import get_runner
TestRunner = get_runner(settings)
test_runner = TestRunner(
- verbosity = int(options.verbosity),
- interactive = options.interactive,
- failfast = options.failfast,
+ verbosity=int(options.verbosity),
+ interactive=options.interactive,
+ failfast=options.failfast,
)
# Run the tests.
failures = test_runner.run_tests(["test_watson"])
diff --git a/src/tests/test_watson/__init__.py b/tests/test_watson/__init__.py
similarity index 100%
rename from src/tests/test_watson/__init__.py
rename to tests/test_watson/__init__.py
diff --git a/src/tests/test_watson/admin.py b/tests/test_watson/admin.py
similarity index 100%
rename from src/tests/test_watson/admin.py
rename to tests/test_watson/admin.py
diff --git a/src/tests/test_watson/models.py b/tests/test_watson/models.py
similarity index 71%
rename from src/tests/test_watson/models.py
rename to tests/test_watson/models.py
index 2241c17..1b88778 100644
--- a/src/tests/test_watson/models.py
+++ b/tests/test_watson/models.py
@@ -4,47 +4,46 @@ from django.utils.encoding import force_text, python_2_unicode_compatible
@python_2_unicode_compatible
class TestModelBase(models.Model):
-
title = models.CharField(
- max_length = 200,
+ max_length=200,
)
-
+
content = models.TextField(
- blank = True,
+ blank=True,
)
-
+
description = models.TextField(
- blank = True,
+ blank=True,
)
-
+
is_published = models.BooleanField(
- default = True,
+ default=True,
)
-
+
def __str__(self):
return force_text(self.title)
class Meta:
abstract = True
-
+
class WatsonTestModel1(TestModelBase):
pass
-str_pk_gen = 0;
+str_pk_gen = 0
+
def get_str_pk():
global str_pk_gen
- str_pk_gen += 1;
+ str_pk_gen += 1
return str(str_pk_gen)
-
+
class WatsonTestModel2(TestModelBase):
-
id = models.CharField(
- primary_key = True,
- max_length = 100,
- default = get_str_pk
+ primary_key=True,
+ max_length=100,
+ default=get_str_pk
)
diff --git a/src/tests/test_watson/tests.py b/tests/test_watson/tests.py
similarity index 90%
rename from src/tests/test_watson/tests.py
rename to tests/test_watson/tests.py
index bc2d58b..82121a8 100644
--- a/src/tests/test_watson/tests.py
+++ b/tests/test_watson/tests.py
@@ -29,7 +29,7 @@ from watson.models import SearchEntry
from watson.backends import escape_query
from test_watson.models import WatsonTestModel1, WatsonTestModel2
-from test_watson import admin # Force early registration of all admin models.
+from test_watson import admin # Force early registration of all admin models. # noQA
class RegistrationTest(TestCase):
@@ -105,28 +105,32 @@ class SearchTestBase(TestCase):
# Register the test models.
watson.register(self.model1)
watson.register(self.model2, exclude=("id",))
- complex_registration_search_engine.register(WatsonTestModel1, exclude=("content", "description",), store=("is_published",))
- complex_registration_search_engine.register(WatsonTestModel2, fields=("title",))
+ complex_registration_search_engine.register(
+ WatsonTestModel1, exclude=("content", "description",), store=("is_published",)
+ )
+ complex_registration_search_engine.register(
+ WatsonTestModel2, fields=("title",)
+ )
# Create some test models.
self.test11 = WatsonTestModel1.objects.create(
- title = "title model1 instance11",
- content = "content model1 instance11",
- description = "description model1 instance11",
+ title="title model1 instance11",
+ content="content model1 instance11",
+ description="description model1 instance11",
)
self.test12 = WatsonTestModel1.objects.create(
- title = "title model1 instance12",
- content = "content model1 instance12",
- description = "description model1 instance12",
+ title="title model1 instance12",
+ content="content model1 instance12",
+ description="description model1 instance12",
)
self.test21 = WatsonTestModel2.objects.create(
- title = "title model2 instance21",
- content = "content model2 instance21",
- description = "description model2 instance21",
+ title="title model2 instance21",
+ content="content model2 instance21",
+ description="description model2 instance21",
)
self.test22 = WatsonTestModel2.objects.create(
- title = "title model2 instance22",
- content = "content model2 instance22",
- description = "description model2 instance22",
+ title="title model2 instance22",
+ content="content model2 instance22",
+ description="description model2 instance22",
)
def tearDown(self):
@@ -366,8 +370,10 @@ class SearchTest(SearchTestBase):
self.assertEqual(watson.search("abcd@efgh").count(), 1)
x.delete()
-
- @skipUnless(watson.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)
@@ -411,67 +417,83 @@ class SearchTest(SearchTestBase):
def testLimitedModelQuerySet(self):
# Test a search that should get all models.
- self.assertEqual(watson.search("TITLE", models=(WatsonTestModel1.objects.filter(title__icontains="TITLE"), WatsonTestModel2.objects.filter(title__icontains="TITLE"),)).count(), 4)
+ self.assertEqual(watson.search(
+ "TITLE",
+ models=(
+ WatsonTestModel1.objects.filter(title__icontains="TITLE"),
+ WatsonTestModel2.objects.filter(title__icontains="TITLE"),
+ )
+ ).count(), 4)
# Test a search that should get two models.
- self.assertEqual(watson.search("MODEL1", models=(WatsonTestModel1.objects.filter(
- title__icontains = "MODEL1",
- description__icontains = "MODEL1",
- ),)).count(), 2)
+ self.assertEqual(
+ watson.search(
+ "MODEL1",
+ models=(WatsonTestModel1.objects.filter(
+ title__icontains="MODEL1",
+ description__icontains="MODEL1",
+ ),)
+ ).count(), 2)
self.assertEqual(watson.search("MODEL2", models=(WatsonTestModel2.objects.filter(
- title__icontains = "MODEL2",
- description__icontains = "MODEL2",
+ title__icontains="MODEL2",
+ description__icontains="MODEL2",
),)).count(), 2)
# Test a search that should get one model.
self.assertEqual(watson.search("INSTANCE11", models=(WatsonTestModel1.objects.filter(
- title__icontains = "MODEL1",
+ title__icontains="MODEL1",
),)).count(), 1)
self.assertEqual(watson.search("INSTANCE21", models=(WatsonTestModel2.objects.filter(
- title__icontains = "MODEL2",
+ title__icontains="MODEL2",
),)).count(), 1)
# Test a search that should get no models.
self.assertEqual(watson.search("INSTANCE11", models=(WatsonTestModel1.objects.filter(
- title__icontains = "MODEL2",
+ title__icontains="MODEL2",
),)).count(), 0)
self.assertEqual(watson.search("INSTANCE21", models=(WatsonTestModel2.objects.filter(
- title__icontains = "MODEL1",
+ 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)
+ self.assertEqual(
+ watson.search(
+ "TITLE",
+ exclude=(
+ WatsonTestModel1.objects.filter(title__icontains="FOOO"),
+ WatsonTestModel2.objects.filter(title__icontains="FOOO"),)
+ ).count(), 4)
# Test a search that should get two models.
self.assertEqual(watson.search("MODEL1", exclude=(WatsonTestModel1.objects.filter(
- title__icontains = "INSTANCE21",
- description__icontains = "INSTANCE22",
+ title__icontains="INSTANCE21",
+ description__icontains="INSTANCE22",
),)).count(), 2)
self.assertEqual(watson.search("MODEL2", exclude=(WatsonTestModel2.objects.filter(
- title__icontains = "INSTANCE11",
- description__icontains = "INSTANCE12",
+ title__icontains="INSTANCE11",
+ description__icontains="INSTANCE12",
),)).count(), 2)
# Test a search that should get one model.
self.assertEqual(watson.search("INSTANCE11", exclude=(WatsonTestModel1.objects.filter(
- title__icontains = "MODEL2",
+ title__icontains="MODEL2",
),)).count(), 1)
self.assertEqual(watson.search("INSTANCE21", exclude=(WatsonTestModel2.objects.filter(
- title__icontains = "MODEL1",
+ title__icontains="MODEL1",
),)).count(), 1)
# Test a search that should get no models.
self.assertEqual(watson.search("INSTANCE11", exclude=(WatsonTestModel1.objects.filter(
- title__icontains = "MODEL1",
+ title__icontains="MODEL1",
),)).count(), 0)
self.assertEqual(watson.search("INSTANCE21", exclude=(WatsonTestModel2.objects.filter(
- title__icontains = "MODEL2",
+ 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(
"INSTANCE11",
- models = (
+ models=(
WatsonTestModel1.objects.filter(title__icontains="INSTANCE11"),
WatsonTestModel2.objects.filter(title__icontains="TITLE"),
),
- exclude = (
+ exclude=(
WatsonTestModel1.objects.filter(title__icontains="MODEL2"),
WatsonTestModel2.objects.filter(title__icontains="MODEL1"),
)
@@ -500,7 +522,10 @@ class LiveFilterSearchTest(SearchTest):
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)
+ self.assertEqual(watson.search(
+ "tItle Content Description",
+ models=(WatsonTestModel2, WatsonTestModel1._base_manager.all(),)
+ ).count(), 4)
class RankingTest(SearchTestBase):
@@ -522,7 +547,10 @@ class RankingTest(SearchTestBase):
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)
+ self.assertRaises(
+ AttributeError,
+ lambda: watson.filter(WatsonTestModel1, "TITLE", ranking=False)[0].watson_rank
+ )
@skipUnless(watson.get_backend().supports_ranking, "search backend does not support ranking")
def testRankingWithSearch(self):
@@ -545,7 +573,10 @@ class ComplexRegistrationTest(SearchTestBase):
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"])
+ 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)
@@ -562,13 +593,13 @@ class ComplexRegistrationTest(SearchTestBase):
class AdminIntegrationTest(SearchTestBase):
-
+
def setUp(self):
super(AdminIntegrationTest, self).setUp()
self.user = User(
- username = "foo",
- is_staff = True,
- is_superuser = True,
+ username="foo",
+ is_staff=True,
+ is_superuser=True,
)
self.user.set_password("bar")
self.user.save()
@@ -577,8 +608,8 @@ class AdminIntegrationTest(SearchTestBase):
def testAdminIntegration(self):
# Log the user in.
self.client.login(
- username = "foo",
- password = "bar",
+ username="foo",
+ password="bar",
)
# Test a search with no query.
response = self.client.get("/admin/test_watson/watsontestmodel1/")
diff --git a/src/tests/test_watson/urls.py b/tests/test_watson/urls.py
similarity index 97%
rename from src/tests/test_watson/urls.py
rename to tests/test_watson/urls.py
index 42b01c0..ad09f1f 100644
--- a/src/tests/test_watson/urls.py
+++ b/tests/test_watson/urls.py
@@ -5,7 +5,7 @@ from django.contrib import admin
urlpatterns = [
url("^simple/", include("watson.urls")),
-
+
url("^custom/", include("watson.urls"), kwargs={
"query_param": "fooo",
"empty_query_redirect": "/simple/",
@@ -15,6 +15,6 @@ urlpatterns = [
},
"paginate_by": 10,
}),
-
+
url("^admin/", include(admin.site.urls)),
]
diff --git a/src/tests/urls.py b/tests/urls.py
similarity index 100%
rename from src/tests/urls.py
rename to tests/urls.py
diff --git a/tox.ini b/tox.ini
index 1221429..a1855c7 100644
--- a/tox.ini
+++ b/tox.ini
@@ -12,6 +12,10 @@ deps =
postgres: psycopg2
mysql: mysqlclient
commands =
- sqlite: coverage run src/tests/runtests.py
- postgres: coverage run src/tests/runtests.py -d psql
- mysql: coverage run src/tests/runtests.py -d mysql
+ sqlite: coverage run tests/runtests.py
+ postgres: coverage run tests/runtests.py -d psql
+ mysql: coverage run tests/runtests.py -d mysql
+
+[flake8]
+max-line-length=120
+exclude=build,venv,migrations,south_migrations,.tox
diff --git a/src/watson/admin.py b/watson/admin.py
similarity index 93%
rename from src/watson/admin.py
rename to watson/admin.py
index fee50ee..7cdc1c7 100644
--- a/src/watson/admin.py
+++ b/watson/admin.py
@@ -68,9 +68,9 @@ class SearchAdmin(admin.ModelAdmin):
if not self.search_engine.is_registered(self.model) and self.search_fields:
self.search_engine.register(
self.model,
- fields = self.search_fields,
- adapter_cls = self.search_adapter_cls,
- get_live_queryset = lambda self_: None, # Ensure complete queryset is used in admin.
+ fields=self.search_fields,
+ adapter_cls=self.search_adapter_cls,
+ get_live_queryset=lambda self_: None, # Ensure complete queryset is used in admin.
)
def get_changelist(self, request, **kwargs):
diff --git a/src/watson/backends.py b/watson/backends.py
similarity index 93%
rename from src/watson/backends.py
rename to watson/backends.py
index 8ec1c4a..0900119 100644
--- a/src/watson/backends.py
+++ b/watson/backends.py
@@ -118,7 +118,7 @@ class RegexSearchMixin(six.with_metaclass(abc.ABCMeta)):
""", """
({db_table}.{content_type_id} = %s)
"""]
- word_kwargs= {
+ word_kwargs = {
"db_table": db_table,
"model_db_table": model_db_table,
"engine_slug": connection.ops.quote_name("engine_slug"),
@@ -147,9 +147,13 @@ class RegexSearchMixin(six.with_metaclass(abc.ABCMeta)):
# Add in all words.
for word in search_text.split():
regex = regex_from_word(word)
- word_query.append("""
- ({db_table}.{title} {iregex_operator} OR {db_table}.{description} {iregex_operator} OR {db_table}.{content} {iregex_operator})
- """)
+ word_query.append(
+ """
+ ({db_table}.{title} {iregex_operator}
+ OR {db_table}.{description} {iregex_operator}
+ OR {db_table}.{content} {iregex_operator})
+ """
+ )
word_args.extend((regex, regex, regex))
# Compile the query.
full_word_query = " AND ".join(word_query).format(**word_kwargs)
@@ -354,25 +358,36 @@ class MySQLSearchBackend(SearchBackend):
def is_installed(self):
"""Checks whether django-watson is installed."""
cursor = connection.cursor()
- cursor.execute("SHOW INDEX FROM watson_searchentry WHERE Key_name = 'watson_searchentry_fulltext'");
+ cursor.execute("SHOW INDEX FROM watson_searchentry WHERE Key_name = 'watson_searchentry_fulltext'")
return bool(cursor.fetchall())
def do_install(self):
"""Executes the MySQL specific SQL code to install django-watson."""
cursor = connection.cursor()
# Drop all foreign keys on the watson_searchentry table.
- cursor.execute("SELECT CONSTRAINT_NAME FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA = DATABASE() AND TABLE_NAME = 'watson_searchentry' AND CONSTRAINT_TYPE = 'FOREIGN KEY'")
+ cursor.execute(
+ "SELECT CONSTRAINT_NAME FROM information_schema.TABLE_CONSTRAINTS "
+ "WHERE CONSTRAINT_SCHEMA = DATABASE() "
+ "AND TABLE_NAME = 'watson_searchentry' "
+ "AND CONSTRAINT_TYPE = 'FOREIGN KEY'"
+ )
for constraint_name, in cursor.fetchall():
- cursor.execute("ALTER TABLE watson_searchentry DROP FOREIGN KEY {constraint_name}".format(
- constraint_name=constraint_name,
- ))
+ cursor.execute(
+ "ALTER TABLE watson_searchentry DROP FOREIGN KEY {constraint_name}".format(
+ constraint_name=constraint_name,
+ )
+ )
# Change the storage engine to MyISAM.
cursor.execute("ALTER TABLE watson_searchentry ENGINE = MyISAM")
# Add the full text indexes.
- cursor.execute("CREATE FULLTEXT INDEX watson_searchentry_fulltext ON watson_searchentry (title, description, content)")
- cursor.execute("CREATE FULLTEXT INDEX watson_searchentry_title ON watson_searchentry (title)")
- cursor.execute("CREATE FULLTEXT INDEX watson_searchentry_description ON watson_searchentry (description)")
- cursor.execute("CREATE FULLTEXT INDEX watson_searchentry_content ON watson_searchentry (content)")
+ cursor.execute("CREATE FULLTEXT INDEX watson_searchentry_fulltext "
+ "ON watson_searchentry (title, description, content)")
+ cursor.execute("CREATE FULLTEXT INDEX watson_searchentry_title "
+ "ON watson_searchentry (title)")
+ cursor.execute("CREATE FULLTEXT INDEX watson_searchentry_description "
+ "ON watson_searchentry (description)")
+ cursor.execute("CREATE FULLTEXT INDEX watson_searchentry_content "
+ "ON watson_searchentry (content)")
def do_uninstall(self):
"""Executes the SQL needed to uninstall django-watson."""
@@ -427,7 +442,8 @@ class MySQLSearchBackend(SearchBackend):
tables=("watson_searchentry",),
where=(
"watson_searchentry.engine_slug = %s",
- "MATCH (watson_searchentry.title, watson_searchentry.description, watson_searchentry.content) AGAINST (%s IN BOOLEAN MODE)",
+ "MATCH (watson_searchentry.title, watson_searchentry.description, watson_searchentry.content) "
+ "AGAINST (%s IN BOOLEAN MODE)",
"watson_searchentry.{ref_name} = {table_name}.{pk_name}".format(
ref_name=ref_name,
table_name=connection.ops.quote_name(model._meta.db_table),
diff --git a/src/watson/management/__init__.py b/watson/management/__init__.py
similarity index 100%
rename from src/watson/management/__init__.py
rename to watson/management/__init__.py
diff --git a/src/watson/management/commands/__init__.py b/watson/management/commands/__init__.py
similarity index 100%
rename from src/watson/management/commands/__init__.py
rename to watson/management/commands/__init__.py
diff --git a/src/watson/management/commands/buildwatson.py b/watson/management/commands/buildwatson.py
similarity index 71%
rename from src/watson/management/commands/buildwatson.py
rename to watson/management/commands/buildwatson.py
index de257c1..59703d2 100644
--- a/src/watson/management/commands/buildwatson.py
+++ b/watson/management/commands/buildwatson.py
@@ -2,8 +2,6 @@
from __future__ import unicode_literals, print_function
-from optparse import make_option
-
from django.core.management.base import BaseCommand, CommandError
from django.apps import apps
from django.contrib import admin
@@ -21,56 +19,68 @@ from watson.models import SearchEntry
# Sets up registration for django-watson's admin integration.
admin.autodiscover()
+
def get_engine(engine_slug_):
- '''returns search engine with a given name'''
+ """returns search engine with a given name"""
try:
return [x[1] for x in SearchEngine.get_created_engines() if x[0] == engine_slug_][0]
except IndexError:
raise CommandError("Search Engine \"%s\" is not registered!" % force_text(engine_slug_))
+
def rebuild_index_for_model(model_, engine_slug_, verbosity_):
- '''rebuilds index for a model'''
+ """rebuilds index for a model"""
search_engine_ = get_engine(engine_slug_)
local_refreshed_model_count = [0] # HACK: Allows assignment to outer scope.
+
def iter_search_entries():
for obj in model_._default_manager.all().iterator():
for search_entry in search_engine_._update_obj_index_iter(obj):
yield search_entry
local_refreshed_model_count[0] += 1
if verbosity_ >= 3:
- print("Refreshed search entry for {model} {obj} in {engine_slug!r} search engine.".format(
- model = force_text(model_._meta.verbose_name),
- obj = force_text(obj),
- engine_slug = force_text(engine_slug_),
- ))
+ print(
+ "Refreshed search entry for {model} {obj} "
+ "in {engine_slug!r} search engine.".format(
+ model=force_text(model_._meta.verbose_name),
+ obj=force_text(obj),
+ engine_slug=force_text(engine_slug_),
+ )
+ )
if verbosity_ == 2:
- print("Refreshed {local_refreshed_model_count} {model} search entry(s) in {engine_slug!r} search engine.".format(
- model = force_text(model_._meta.verbose_name),
- local_refreshed_model_count = local_refreshed_model_count[0],
- engine_slug = force_text(engine_slug_),
- ))
+ print(
+ "Refreshed {local_refreshed_model_count} {model} search entry(s) "
+ "in {engine_slug!r} search engine.".format(
+ model=force_text(model_._meta.verbose_name),
+ local_refreshed_model_count=local_refreshed_model_count[0],
+ engine_slug=force_text(engine_slug_),
+ )
+ )
_bulk_save_search_entries(iter_search_entries())
return local_refreshed_model_count[0]
+
class Command(BaseCommand):
args = "[[--engine=search_engine] ... ]"
- help = "Rebuilds the database indices needed by django-watson. You can (re-)build index for selected models by specifying them"
+ help = "Rebuilds the database indices needed by django-watson. " \
+ "You can (re-)build index for selected models by specifying them"
def add_arguments(self, parser):
parser.add_argument("apps", nargs="*", action="store", default=[])
- parser.add_argument('--engine',
+ parser.add_argument(
+ '--engine',
action="store",
help='Search engine models are registered with'
)
-
+
@transaction.atomic()
def handle(self, *args, **options):
"""Runs the management command."""
activate(settings.LANGUAGE_CODE)
verbosity = int(options.get("verbosity", 1))
-
+
# see if we're asked to use a specific search engine
if options.get('engine'):
engine_slug = options['engine']
@@ -78,13 +88,13 @@ class Command(BaseCommand):
else:
engine_slug = "default"
engine_selected = False
-
+
# work-around for legacy optparser hack in BaseCommand. In Django=1.10 the
# args are collected in options['apps'], but in earlier versions they are
# kept in args.
- if len(options['apps']):
+ if len(options['apps']):
args = options['apps']
-
+
# get the search engine we'll be checking registered models for, may be "default"
search_engine = get_engine(engine_slug)
models = []
@@ -101,7 +111,10 @@ class Command(BaseCommand):
else:
model = None
if model is None or not search_engine.is_registered(model):
- raise CommandError("Model \"%s\" is not registered with django-watson search engine \"%s\"!" % (force_text(model_name), force_text(engine_slug)))
+ raise CommandError(
+ "Model \"%s\" is not registered with django-watson search engine \"%s\"!"
+ % (force_text(model_name), force_text(engine_slug))
+ )
models.append(model)
refreshed_model_count = 0
@@ -128,24 +141,31 @@ class Command(BaseCommand):
for model in registered_models:
refreshed_model_count += rebuild_index_for_model(model, engine_slug, verbosity)
- # Clean out any search entries that exist for stale content types. Only do it during full rebuild
+ # Clean out any search entries that exist for stale content types.
+ # Only do it during full rebuild
valid_content_types = [ContentType.objects.get_for_model(model) for model in registered_models]
stale_entries = SearchEntry.objects.filter(
- engine_slug = engine_slug,
+ engine_slug=engine_slug,
).exclude(
- content_type__in = valid_content_types
+ content_type__in=valid_content_types
)
stale_entry_count = stale_entries.count()
if stale_entry_count > 0:
stale_entries.delete()
if verbosity >= 1:
- print("Deleted {stale_entry_count} stale search entry(s) in {engine_slug!r} search engine.".format(
- stale_entry_count = stale_entry_count,
- engine_slug = force_text(engine_slug),
- ))
+ print(
+ "Deleted {stale_entry_count} stale search entry(s) "
+ "in {engine_slug!r} search engine.".format(
+ stale_entry_count=stale_entry_count,
+ engine_slug=force_text(engine_slug),
+ )
+ )
if verbosity == 1:
- print("Refreshed {refreshed_model_count} search entry(s) in {engine_slug!r} search engine.".format(
- refreshed_model_count = refreshed_model_count,
- engine_slug = force_text(engine_slug),
- ))
+ print(
+ "Refreshed {refreshed_model_count} search entry(s) "
+ "in {engine_slug!r} search engine.".format(
+ refreshed_model_count=refreshed_model_count,
+ engine_slug=force_text(engine_slug),
+ )
+ )
diff --git a/src/watson/management/commands/installwatson.py b/watson/management/commands/installwatson.py
similarity index 100%
rename from src/watson/management/commands/installwatson.py
rename to watson/management/commands/installwatson.py
diff --git a/src/watson/management/commands/listwatson.py b/watson/management/commands/listwatson.py
similarity index 99%
rename from src/watson/management/commands/listwatson.py
rename to watson/management/commands/listwatson.py
index 2bc0939..9b47707 100644
--- a/src/watson/management/commands/listwatson.py
+++ b/watson/management/commands/listwatson.py
@@ -3,6 +3,7 @@
from django.core.management.base import BaseCommand
from watson import search as watson
+
class Command(BaseCommand):
help = "List all registed models by django-watson."
@@ -12,5 +13,3 @@ class Command(BaseCommand):
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/watson/management/commands/uninstallwatson.py
similarity index 100%
rename from src/watson/management/commands/uninstallwatson.py
rename to watson/management/commands/uninstallwatson.py
diff --git a/src/watson/middleware.py b/watson/middleware.py
similarity index 100%
rename from src/watson/middleware.py
rename to watson/middleware.py
diff --git a/src/watson/migrations/0001_initial.py b/watson/migrations/0001_initial.py
similarity index 100%
rename from src/watson/migrations/0001_initial.py
rename to watson/migrations/0001_initial.py
diff --git a/src/watson/migrations/__init__.py b/watson/migrations/__init__.py
similarity index 100%
rename from src/watson/migrations/__init__.py
rename to watson/migrations/__init__.py
diff --git a/src/watson/models.py b/watson/models.py
similarity index 89%
rename from src/watson/models.py
rename to watson/models.py
index 108993c..fb3d382 100644
--- a/src/watson/models.py
+++ b/watson/models.py
@@ -2,8 +2,6 @@
from __future__ import unicode_literals
-import json
-
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.utils.functional import cached_property
@@ -13,6 +11,7 @@ try:
except ImportError:
from django.contrib.contenttypes.generic import GenericForeignKey
+
def has_int_pk(model):
"""Tests whether the given model has an integer primary key."""
pk = model._meta.pk
@@ -34,9 +33,9 @@ class SearchEntry(models.Model):
"""An entry in the search index."""
engine_slug = models.CharField(
- max_length = 200,
- db_index = True,
- default = "default",
+ max_length=200,
+ db_index=True,
+ default="default",
)
content_type = models.ForeignKey(
@@ -46,28 +45,28 @@ class SearchEntry(models.Model):
object_id = models.TextField()
object_id_int = models.IntegerField(
- blank = True,
- null = True,
- db_index = True,
+ blank=True,
+ null=True,
+ db_index=True,
)
object = GenericForeignKey()
title = models.CharField(
- max_length = 1000,
+ max_length=1000,
)
description = models.TextField(
- blank = True,
+ blank=True,
)
content = models.TextField(
- blank = True,
+ blank=True,
)
url = models.CharField(
- max_length = 1000,
- blank = True,
+ max_length=1000,
+ blank=True,
)
meta_encoded = models.TextField()
diff --git a/src/watson/search.py b/watson/search.py
similarity index 87%
rename from src/watson/search.py
rename to watson/search.py
index de56b33..a040584 100644
--- a/src/watson/search.py
+++ b/watson/search.py
@@ -2,7 +2,8 @@
from __future__ import unicode_literals
-import sys, json
+import json
+import sys
from itertools import chain, islice
from threading import local
from functools import wraps
@@ -63,11 +64,14 @@ class SearchAdapter(object):
try:
value = getattr(self, prefix)
except AttributeError:
- raise SearchAdapterError("Could not find a property called {name!r} on either {obj!r} or {search_adapter!r}".format(
- name = prefix,
- obj = obj,
- search_adapter = self,
- ))
+ raise SearchAdapterError(
+ "Could not find a property called {name!r}"
+ " on either {obj!r} or {search_adapter!r}".format(
+ name=prefix,
+ obj=obj,
+ search_adapter=self,
+ )
+ )
else:
# Run the attribute on the search adapter, if it's callable.
if not isinstance(value, (QuerySet, models.Manager)):
@@ -97,7 +101,8 @@ class SearchAdapter(object):
def get_title(self, obj):
"""
- Returns the title of this search result. This is given high priority in search result ranking.
+ Returns the title of this search result.
+ This is given high priority in search result ranking.
You can access the title of the search entry as `entry.title` in your search results.
@@ -107,11 +112,12 @@ class SearchAdapter(object):
def get_description(self, obj):
"""
- Returns the description of this search result. This is given medium priority in search result ranking.
+ Returns the description of this search result.
+ This is given medium priority in search result ranking.
- You can access the description of the search entry as `entry.description` in your search results. Since
- this should contains a short description of the search entry, it's excellent for providing a summary
- in your search results.
+ You can access the description of the search entry as `entry.description`
+ in your search results. Since this should contains a short description of the search entry,
+ it's excellent for providing a summary in your search results.
The default implementation returns `""`.
"""
@@ -119,15 +125,20 @@ class SearchAdapter(object):
def get_content(self, obj):
"""
- Returns the content of this search result. This is given low priority in search result ranking.
+ Returns the content of this search result.
+ This is given low priority in search result ranking.
- You can access the content of the search entry as `entry.content` in your search results, although
- this field generally contains a big mess of search data so is less suitable for frontend display.
+ You can access the content of the search entry as `entry.content` in your search results,
+ although this field generally contains a big mess of search data so is less suitable
+ for frontend display.
The default implementation returns all the registered fields in your model joined together.
"""
# Get the field names to look up.
- field_names = self.fields or (field.name for field in self.model._meta.fields if isinstance(field, (models.CharField, models.TextField)))
+ field_names = self.fields or (
+ field.name for field in self.model._meta.fields if
+ isinstance(field, (models.CharField, models.TextField))
+ )
# Exclude named fields.
field_names = (field_name for field_name in field_names if field_name not in self.exclude)
# Create the text.
@@ -244,7 +255,11 @@ class SearchContextManager(local):
# Save all the models.
tasks, is_invalid = self._stack.pop()
if not is_invalid:
- _bulk_save_search_entries(list(chain.from_iterable(engine._update_obj_index_iter(obj) for engine, obj in tasks)))
+ _bulk_save_search_entries(
+ list(chain.from_iterable(engine._update_obj_index_iter(obj)
+ for engine, obj in tasks)
+ )
+ )
# Context management.
@@ -345,9 +360,11 @@ class SearchEngine(object):
"""Initializes the search engine."""
# Check the slug is unique for this project.
if engine_slug in SearchEngine._created_engines:
- raise SearchEngineError("A search engine has already been created with the slug {engine_slug!r}".format(
- engine_slug = engine_slug,
- ))
+ raise SearchEngineError(
+ "A search engine has already been created with the slug {engine_slug!r}".format(
+ engine_slug=engine_slug,
+ )
+ )
# Initialize thie engine.
self._registered_models = {}
self._engine_slug = engine_slug
@@ -374,13 +391,16 @@ class SearchEngine(object):
field_overrides["get_live_queryset"] = lambda self_: live_queryset.all()
# Check for existing registration.
if self.is_registered(model):
- raise RegistrationError("{model!r} is already registered with this search engine".format(
- model = model,
- ))
+ raise RegistrationError(
+ "{model!r} is already registered with this search engine".format(
+ model=model,
+ ))
# Perform any customization.
if field_overrides:
# Conversion to str is needed because Python 2 doesn't accept unicode for class name
- adapter_cls = type(str("Custom") + adapter_cls.__name__, (adapter_cls,), field_overrides)
+ adapter_cls = type(
+ str("Custom") + adapter_cls.__name__, (adapter_cls,), field_overrides
+ )
# Perform the registration.
adapter_obj = adapter_cls(model)
self._registered_models[model] = adapter_obj
@@ -401,7 +421,7 @@ class SearchEngine(object):
# Check for registration.
if not self.is_registered(model):
raise RegistrationError("{model!r} is not registered with this search engine".format(
- model = model,
+ model=model,
))
# Perform the unregistration.
del self._registered_models[model]
@@ -418,7 +438,7 @@ class SearchEngine(object):
if self.is_registered(model):
return self._registered_models[model]
raise RegistrationError("{model!r} is not registered with this search engine".format(
- model = model,
+ model=model,
))
def _get_entries_for_obj(self, obj):
@@ -430,20 +450,20 @@ class SearchEngine(object):
object_id = force_text(obj.pk)
# Get the basic list of search entries.
search_entries = SearchEntry.objects.filter(
- content_type = content_type,
- engine_slug = self._engine_slug,
+ content_type=content_type,
+ engine_slug=self._engine_slug,
)
if has_int_pk(model):
# Do a fast indexed lookup.
object_id_int = int(obj.pk)
search_entries = search_entries.filter(
- object_id_int = object_id_int,
+ object_id_int=object_id_int,
)
else:
# Alas, have to do a slow unindexed lookup.
object_id_int = None
search_entries = search_entries.filter(
- object_id = object_id,
+ object_id=object_id,
)
return object_id_int, search_entries
@@ -514,31 +534,35 @@ class SearchEngine(object):
queryset = sub_queryset.values_list("pk", flat=True)
if has_int_pk(model):
filter &= Q(
- object_id_int__in = queryset,
+ object_id_int__in=queryset,
)
else:
live_ids = list(queryset)
if live_ids:
filter &= Q(
- object_id__in = live_ids,
+ object_id__in=live_ids,
)
else:
- # HACK: There is a bug in Django (https://code.djangoproject.com/ticket/15145) that messes up __in queries when the iterable is empty.
- # This bit of nonsense ensures that this aspect of the query will be impossible to fulfill.
+ # HACK: There is a bug in Django
+ # (https://code.djangoproject.com/ticket/15145)
+ # that messes up __in queries when the iterable is empty.
+ # This bit of nonsense ensures that this aspect of the query
+ # will be impossible to fulfill.
filter &= Q(
- content_type = ContentType.objects.get_for_model(model).id + 1,
+ content_type=ContentType.objects.get_for_model(model).id + 1,
)
# Add the model to the filter.
content_type = ContentType.objects.get_for_model(model)
filter &= Q(
- content_type = content_type,
+ content_type=content_type,
)
# Combine with the other filters.
filters |= filter
return filters
def _get_included_models(self, models):
- """Returns an iterable of models and querysets that should be included in the search query."""
+ """Returns an iterable of models and querysets that should be included
+ in the search query."""
for model in models or self.get_registered_models():
if isinstance(model, QuerySet):
yield model
@@ -559,7 +583,7 @@ class SearchEngine(object):
return SearchEntry.objects.none()
# Get the initial queryset.
queryset = SearchEntry.objects.filter(
- engine_slug = self._engine_slug,
+ engine_slug=self._engine_slug,
)
# Process the allowed models.
queryset = queryset.filter(
@@ -620,10 +644,11 @@ def get_backend(backend_name=None):
try:
backend_cls = getattr(backend_module, backend_cls_name)
except AttributeError:
- raise ImproperlyConfigured("Could not find a class named {backend_module_name!r} in {backend_cls_name!r}".format(
- backend_module_name = backend_module_name,
- backend_cls_name = backend_cls_name,
- ))
+ raise ImproperlyConfigured(
+ "Could not find a class named {backend_module_name!r} in {backend_cls_name!r}".format(
+ backend_module_name=backend_module_name,
+ backend_cls_name=backend_cls_name,
+ ))
# Initialize the backend.
backend = backend_cls()
_backends_cache[backend_name] = backend
diff --git a/src/watson/south_migrations/0001_initial.py b/watson/south_migrations/0001_initial.py
similarity index 100%
rename from src/watson/south_migrations/0001_initial.py
rename to watson/south_migrations/0001_initial.py
diff --git a/src/watson/south_migrations/0002_installwatson.py b/watson/south_migrations/0002_installwatson.py
similarity index 100%
rename from src/watson/south_migrations/0002_installwatson.py
rename to watson/south_migrations/0002_installwatson.py
diff --git a/src/watson/south_migrations/__init__.py b/watson/south_migrations/__init__.py
similarity index 100%
rename from src/watson/south_migrations/__init__.py
rename to watson/south_migrations/__init__.py
diff --git a/src/watson/templates/watson/includes/search_result_item.html b/watson/templates/watson/includes/search_result_item.html
similarity index 100%
rename from src/watson/templates/watson/includes/search_result_item.html
rename to watson/templates/watson/includes/search_result_item.html
diff --git a/src/watson/templates/watson/includes/search_results.html b/watson/templates/watson/includes/search_results.html
similarity index 100%
rename from src/watson/templates/watson/includes/search_results.html
rename to watson/templates/watson/includes/search_results.html
diff --git a/src/watson/templates/watson/search_results.html b/watson/templates/watson/search_results.html
similarity index 100%
rename from src/watson/templates/watson/search_results.html
rename to watson/templates/watson/search_results.html
diff --git a/src/watson/templatetags/__init__.py b/watson/templatetags/__init__.py
similarity index 100%
rename from src/watson/templatetags/__init__.py
rename to watson/templatetags/__init__.py
diff --git a/src/watson/templatetags/watson.py b/watson/templatetags/watson.py
similarity index 100%
rename from src/watson/templatetags/watson.py
rename to watson/templatetags/watson.py
diff --git a/src/watson/urls.py b/watson/urls.py
similarity index 98%
rename from src/watson/urls.py
rename to watson/urls.py
index de2355f..0481726 100644
--- a/src/watson/urls.py
+++ b/watson/urls.py
@@ -9,7 +9,6 @@ from watson.views import search, search_json
urlpatterns = [
url("^$", search, name="search"),
-
url("^json/$", search_json, name="search_json"),
]
diff --git a/src/watson/views.py b/watson/views.py
similarity index 100%
rename from src/watson/views.py
rename to watson/views.py