summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEvgeny Fadeev <evgeny.fadeev@gmail.com>2013-07-12 16:27:01 -0400
committerEvgeny Fadeev <evgeny.fadeev@gmail.com>2013-07-12 16:27:01 -0400
commite2fdbb9bbc28927d61c90d33bde6317dc7f6a452 (patch)
tree7501bc45780dfc7adac9ae29a76cd20177588a87
parent3a55b7ffc282bac409605859e7ec8a3a84790d85 (diff)
parentc1b3274783ef62b344d2392ebc0d07c559f4e8f0 (diff)
downloadaskbot-e2fdbb9bbc28927d61c90d33bde6317dc7f6a452.tar.gz
askbot-e2fdbb9bbc28927d61c90d33bde6317dc7f6a452.tar.bz2
askbot-e2fdbb9bbc28927d61c90d33bde6317dc7f6a452.zip
Merge branch 'github'
-rw-r--r--askbot/doc/source/index.rst1
-rw-r--r--askbot/doc/source/optional-modules.rst5
-rw-r--r--askbot/doc/source/solr.rst182
-rw-r--r--askbot/management/commands/askbot_build_solr_schema.py79
-rw-r--r--askbot/management/commands/askbot_clear_index.py87
-rw-r--r--askbot/management/commands/askbot_rebuild_index.py92
-rw-r--r--askbot/management/commands/askbot_update_index.py86
-rw-r--r--askbot/models/question.py38
-rw-r--r--askbot/search/haystack/__init__.py108
-rw-r--r--askbot/search/haystack/routers.py20
-rw-r--r--askbot/search/haystack/searchquery.py53
-rw-r--r--askbot/search/haystack/signals.py75
-rw-r--r--askbot/search_indexes.py4
-rw-r--r--askbot/setup_templates/settings.py14
-rw-r--r--askbot/setup_templates/settings.py.mustache15
-rw-r--r--askbot/startup_procedures.py32
-rw-r--r--askbot/templates/search/indexes/askbot/post_text.txt1
-rw-r--r--askbot/templates/search/indexes/askbot/thread_text.txt6
-rw-r--r--askbot/templates/search/indexes/auth/user_text.txt5
-rw-r--r--askbot/templates/search_configuration/askbotsolr.xml209
-rw-r--r--askbot/tests/haystack_search_tests.py3
-rw-r--r--askbot/urls.py15
-rw-r--r--askbot/views/widgets.py3
23 files changed, 1043 insertions, 90 deletions
diff --git a/askbot/doc/source/index.rst b/askbot/doc/source/index.rst
index c450ff4b..680ba0aa 100644
--- a/askbot/doc/source/index.rst
+++ b/askbot/doc/source/index.rst
@@ -28,6 +28,7 @@ at the forum_ or by email at admin@askbot.org
Appendix E: Askbot as reusable Django application <askbot-as-reusable-django-application>
Appendix F: Customizing skin in askbot <customizing-skin-in-askbot>
Appendix G: Intranet setup <intranet-setup>
+ Appendix H: Haystack with Solr and Apache Tomcat <solr>
Footnotes <footnotes>
Contributors <contributors>
Changelog <changelog>
diff --git a/askbot/doc/source/optional-modules.rst b/askbot/doc/source/optional-modules.rst
index 995ed224..f4f1e20e 100644
--- a/askbot/doc/source/optional-modules.rst
+++ b/askbot/doc/source/optional-modules.rst
@@ -71,6 +71,11 @@ To enable:
* add ENABLE_HAYSTACK_SEARCH = True in settings.py
* Configure your search backend according to your setup following `this guide <http://django-haystack.readthedocs.org/en/latest/tutorial.html#modify-your-settings-py>`_
+Solr and Multilingual Support
+-------------------------
+
+There is more documentation about solr and multilingual support please visit :ref:`this link <solr>`
+
Embedding video
===============
diff --git a/askbot/doc/source/solr.rst b/askbot/doc/source/solr.rst
new file mode 100644
index 00000000..9db6ba2f
--- /dev/null
+++ b/askbot/doc/source/solr.rst
@@ -0,0 +1,182 @@
+.. _solr:
+
+===========================================================
+Installing Apache Solr with Apache Tomcat 7 in Ubuntu 12.04
+===========================================================
+
+
+This document describes the process of instalation of Apache Solr search engine in Ubuntu Server 12.04
+for askbot use. To follow this steps you must have already askbot installed and running.
+
+Getting the requirements
+========================
+
+We need the following packages installed::
+
+ sudo apt-get install tomcat7 tomcat7-admin
+
+We need to download Apache Solr from the `official site <http://lucene.apache.org/solr/downloads.html>`_::
+
+ wget http://www.bizdirusa.com/mirrors/apache/lucene/solr/3.6.2/apache-solr-3.6.2.tgz
+
+Setting up Tomcat
+=================
+
+After installing tomcat there are some configuration required to make it work. First we are going to add
+Tomcat users. Edit /etc/tomcat7/tomcat-users.xml and add the following::
+
+ <?xml version='1.0' encoding='utf-8'?>
+ <tomcat-users>
+ <role rolename="manager"/>
+ <role rolename="admin"/>
+ <role rolename="admin-gui"/>
+ <role rolename="manager-gui"/>
+ <user username="tomcat" password="tomcat" roles="manager,admin,manager-gui,admin-gui"/>
+ </tomcat-users>
+
+This will allow you to connect to the web management interface. After doing it restart the service:
+
+ service tomcat7 restart
+
+To make see if it works go to: http://youripaddress:8080/manager it will ask for your tomcat user password
+described in the tomcat-users.xml
+
+Installing Solr under Tomcat
+============================
+
+Extract the solr tar archive from the previous download::
+
+ tar -xzf apache-solr-3.6.2.tgz
+
+Copy the example/ directory from the source to /opt/solr/. Open the file /opt/solr/example/solr/conf/solrconfig.xml
+and Modify the dataDir parameter as::
+
+ <dataDir>${solr.data.dir:/opt/solr/example/solr/data}</dataDir>
+
+Copy the .war file in dist directory to /opt/solr::
+
+ cp dist/apache-solr-3.6.2.war /opt/solr
+
+Create solr.xml inside of /etc/tomcat/Catalina/localhost/ with the following contents::
+
+ <?xml version="1.0" encoding="utf-8"?>
+ <Context docBase="/opt/solr/apache-solr-3.6.2.war" debug="0" crossContext="true">
+ <Environment name="solr/home" type="java.lang.String" value="/opt/solr/example/solr" override="true"/>
+ </Context>
+
+Restart tomcat server::
+
+ service tomcat7 restart
+
+By now you should be able to see the "solr" application in the tomcat manager and also access it in /solr/admin.
+
+
+Configuring Askbot with Solr
+============================
+
+Open settings.py file and configure the following::
+
+ ENABLE_HAYSTACK_SEARCH = True
+ HAYSTACK_CONNECTIONS = {
+ 'default': {
+ 'ENGINE': 'haystack.backends.solr_backend.SolrEngine',
+ 'URL': 'http://127.0.0.1:8080/solr'
+ }
+ }
+
+After that create the solr schema and store the output to your solr instalation::
+
+ python manage.py build_solr_schema > /opt/solr/example/solr/conf/schema.xml
+
+Restart tomcat server::
+
+ service tomcat7 restart
+
+Build the Index for the first time::
+
+ python manage.py rebuild_index
+
+The output should be something like::
+
+ All documents removed.
+ Indexing 43 people.
+ Indexing 101 posts.
+ Indexing 101 threads.
+
+You must be good to go after this, just restart the askbot application and test the search with haystack and solr
+
+
+Multilingual Setup
+==================
+
+.. note::
+ This is experimental feature, currently xml generation works for: English, Spanish, Chinese, Japanese, Korean and French.
+
+Add the following to settings.py::
+
+ HAYSTACK_ROUTERS = ['askbot.search.haystack.routers.LanguageRouter',]
+
+Configure the HAYSTACK_CONNECTIONS settings with the following format for each language::
+
+ HAYSTACK_CONNECTIONS = {
+ 'default': {
+ 'ENGINE': 'haystack.backends.solr_backend.SolrEngine',
+ 'URL': 'http://127.0.0.1:8080/solr'
+ },
+ 'default_<language_code>': {
+ 'ENGINE': 'haystack.backends.solr_backend.SolrEngine',
+ 'URL': 'http://127.0.0.1:8080/solr/core-<language_code>'
+ },
+ }
+
+
+Generate xml files according to language::
+
+ python manage.py askbot_build_solr_schema -l <language_code> > /opt/solr/example/solr/conf/schema-<language_code>.xml
+
+Add cores to Solr
+-----------------
+
+For each language that you want to support you will need to add a solr core like this::
+
+ http://127.0.0.1:8080/solr/admin/cores?action=CREATE&name=core-<language_code>&instanceDir=.&config=solrconfig.xml&schema=schema-<language_code>.xml&dataDir=data
+
+For more information on how to handle Solr cores visit `the oficial Solr documetation wiki. <http://wiki.apache.org/solr/CoreAdmin>`_
+
+Build the index according to language
+-------------------------------------
+
+For every language supported you'll need to rebuild the index the following way::
+
+ python manage.py askbot_rebuild_index -l <language_code>
+
+
+Keeping the search index fresh
+==============================
+
+There are several ways to keep the index fresh in askbot with haystack.
+
+Cronjob
+-------
+
+Create a cronjob that executes *askbot_update_index* command for each language installed (in case of multilingual setup).
+
+Real Time Signal
+----------------
+
+The real time signal method updates the index synchronously after each object it's saved or deleted, to enable it add this to settings.py::
+
+ HAYSTACK_SIGNAL_PROCESSOR = 'askbot.search.haystack.signals.AskbotRealtimeSignalProcessor'
+
+this can delay the requests time of your page, if you have a high traffic site this is not recommended.
+
+Updating the Index with Celery
+------------------------------
+
+The real time signal method updates the index asynchronously after each object it's saved or deleted using Celery as queue to enable it add this to settings.py::
+
+ HAYSTACK_SIGNAL_PROCESSOR = 'askbot.search.haystack.signals.AskbotCelerySignalProcessor'
+ #modify CELERY_ALWAYS_EAGER to:
+ CELERY_ALWAYS_EAGER = False
+
+You will need to enable Celery to make this work.
diff --git a/askbot/management/commands/askbot_build_solr_schema.py b/askbot/management/commands/askbot_build_solr_schema.py
new file mode 100644
index 00000000..c7c7a600
--- /dev/null
+++ b/askbot/management/commands/askbot_build_solr_schema.py
@@ -0,0 +1,79 @@
+from optparse import make_option
+import sys
+
+from django.utils.translation import activate as activate_language
+from django.core.exceptions import ImproperlyConfigured
+from django.core.management.base import BaseCommand
+from django.template import loader, Context
+from haystack.backends.solr_backend import SolrSearchBackend
+from haystack.constants import ID, DJANGO_CT, DJANGO_ID, DEFAULT_OPERATOR, DEFAULT_ALIAS
+
+SUPPORTED_LANGUAGES = ['en', 'es', 'ru', 'cn', \
+ 'zn', 'fr', 'jp', 'ko', 'de']
+
+
+class Command(BaseCommand):
+ help = "Generates a Solr schema that reflects the indexes."
+ base_options = (
+ make_option("-f", "--filename", action="store", type="string", dest="filename",
+ help='If provided, directs output to a file instead of stdout.'),
+ make_option("-u", "--using", action="store", type="string", dest="using", default=DEFAULT_ALIAS,
+ help='If provided, chooses a connection to work with.'),
+ make_option("-l", "--language", action="store", type="string", dest="language", default='en',
+ help='Language to user, in language code format')
+ )
+ option_list = BaseCommand.option_list + base_options
+
+ def handle(self, *args, **options):
+ """Generates a Solr schema that reflects the indexes."""
+ using = options.get('using')
+ language = options.get('language')[:2]
+ activate_language(language)
+ if language not in SUPPORTED_LANGUAGES:
+ sys.stderr.write("\n\n")
+ sys.stderr.write("WARNING: your language: '%s' is not supported in our " % language)
+ sys.stderr.write("template it will default to English more information in http://wiki.apache.org/solr/LanguageAnalysis")
+ sys.stderr.write("\n\n")
+ schema_xml = self.build_template(using=using, language=language)
+
+ if options.get('filename'):
+ self.write_file(options.get('filename'), schema_xml)
+ else:
+ self.print_stdout(schema_xml)
+
+ def build_context(self, using, language='en'):
+ from haystack import connections, connection_router
+ backend = connections[using].get_backend()
+
+ if not isinstance(backend, SolrSearchBackend):
+ raise ImproperlyConfigured("'%s' isn't configured as a SolrEngine)." % backend.connection_alias)
+
+ content_field_name, fields = backend.build_schema(connections[using].get_unified_index().all_searchfields())
+ return Context({
+ 'content_field_name': content_field_name,
+ 'fields': fields,
+ 'default_operator': DEFAULT_OPERATOR,
+ 'ID': ID,
+ 'DJANGO_CT': DJANGO_CT,
+ 'DJANGO_ID': DJANGO_ID,
+ 'language': language,
+ })
+
+ def build_template(self, using, language='en'):
+ t = loader.get_template('search_configuration/askbotsolr.xml')
+ c = self.build_context(using=using, language=language)
+ return t.render(c)
+
+ def print_stdout(self, schema_xml):
+ sys.stderr.write("\n")
+ sys.stderr.write("\n")
+ sys.stderr.write("\n")
+ sys.stderr.write("Save the following output to 'schema.xml' and place it in your Solr configuration directory.\n")
+ sys.stderr.write("--------------------------------------------------------------------------------------------\n")
+ sys.stderr.write("\n")
+ print schema_xml
+
+ def write_file(self, filename, schema_xml):
+ schema_file = open(filename, 'w')
+ schema_file.write(schema_xml)
+ schema_file.close()
diff --git a/askbot/management/commands/askbot_clear_index.py b/askbot/management/commands/askbot_clear_index.py
new file mode 100644
index 00000000..bb3b04b9
--- /dev/null
+++ b/askbot/management/commands/askbot_clear_index.py
@@ -0,0 +1,87 @@
+import sys
+from optparse import make_option
+
+from django.core.management import get_commands, load_command_class
+from django.utils.translation import activate as activate_language
+from django.core.management.base import BaseCommand, CommandError
+from django.conf import settings
+
+try:
+ from haystack.management.commands.clear_index import Command as ClearCommand
+ haystack_option_list = [option for option in ClearCommand.base_options if not option.get_opt_string() != '--verbosity']
+except ImportError:
+ haystack_option_list = []
+
+class Command(BaseCommand):
+ help = "Completely rebuilds the search index by removing the old data and then updating."
+ base_options = [make_option("-l", "--language", action="store", type="string", dest="language",
+ help='Language to user, in language code format'),]
+ option_list = list(BaseCommand.option_list) + haystack_option_list + base_options
+
+ def handle(self, **options):
+ lang_code = options.get('language', settings.LANGUAGE_CODE.lower())
+ options['using'] = ['default_%s' % lang_code[:2],]
+ activate_language(lang_code)
+
+ klass = self._get_command_class('clear_index')
+ klass.handle(*args, **options)
+
+ def _get_command_class(self, name):
+ try:
+ app_name = get_commands()[name]
+ if isinstance(app_name, BaseCommand):
+ # If the command is already loaded, use it directly.
+ klass = app_name
+ else:
+ klass = load_command_class(app_name, name)
+ except KeyError:
+ raise CommandError("Unknown command: %r" % name)
+ return klass
+
+
+ def execute(self, *args, **options):
+ """
+ Try to execute this command, performing model validation if
+ needed (as controlled by the attribute
+ ``self.requires_model_validation``). If the command raises a
+ ``CommandError``, intercept it and print it sensibly to
+ stderr.
+ """
+ show_traceback = options.get('traceback', False)
+
+ if self.can_import_settings:
+ try:
+ #language part used to be here
+ pass
+ except ImportError, e:
+ # If settings should be available, but aren't,
+ # raise the error and quit.
+ if show_traceback:
+ traceback.print_exc()
+ else:
+ sys.stderr.write(smart_str(self.style.ERROR('Error: %s\n' % e)))
+ sys.exit(1)
+
+ try:
+ self.stdout = options.get('stdout', sys.stdout)
+ self.stderr = options.get('stderr', sys.stderr)
+ if self.requires_model_validation:
+ self.validate()
+ output = self.handle(*args, **options)
+ if output:
+ if self.output_transaction:
+ # This needs to be imported here, because it relies on
+ # settings.
+ from django.db import connections, DEFAULT_DB_ALIAS
+ connection = connections[options.get('database', DEFAULT_DB_ALIAS)]
+ if connection.ops.start_transaction_sql():
+ self.stdout.write(self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()) + '\n')
+ self.stdout.write(output)
+ if self.output_transaction:
+ self.stdout.write('\n' + self.style.SQL_KEYWORD("COMMIT;") + '\n')
+ except CommandError, e:
+ if show_traceback:
+ traceback.print_exc()
+ else:
+ self.stderr.write(smart_str(self.style.ERROR('Error: %s\n' % e)))
+ sys.exit(1)
diff --git a/askbot/management/commands/askbot_rebuild_index.py b/askbot/management/commands/askbot_rebuild_index.py
new file mode 100644
index 00000000..366bad53
--- /dev/null
+++ b/askbot/management/commands/askbot_rebuild_index.py
@@ -0,0 +1,92 @@
+import sys
+from optparse import make_option
+
+from django.core.management import get_commands, load_command_class
+from django.utils.translation import activate as activate_language
+from django.core.management.base import BaseCommand, CommandError
+from django.conf import settings
+
+try:
+ from haystack.management.commands.clear_index import Command as ClearCommand
+ from haystack.management.commands.update_index import Command as UpdateCommand
+ haystack_option_list = [option for option in UpdateCommand.base_options if option.get_opt_string() != '--verbosity'] + \
+ [option for option in ClearCommand.base_options if not option.get_opt_string() in ['--using', '--verbosity']]
+except ImportError:
+ haystack_option_list = []
+
+class Command(BaseCommand):
+ help = "Completely rebuilds the search index by removing the old data and then updating."
+ base_options = [make_option("-l", "--language", action="store", type="string", dest="language",
+ help='Language to user, in language code format'),]
+ option_list = list(BaseCommand.option_list) + haystack_option_list + base_options
+
+ def handle(self, *args, **options):
+ lang_code = options.get('language', settings.LANGUAGE_CODE.lower())
+ options['using'] = ['default_%s' % lang_code[:2],]
+ activate_language(lang_code)
+
+ klass = self._get_command_class('clear_index')
+ klass.handle(*args, **options)
+
+ klass = self._get_command_class('update_index')
+ klass.handle(*args, **options)
+
+ def _get_command_class(self, name):
+ try:
+ app_name = get_commands()[name]
+ if isinstance(app_name, BaseCommand):
+ # If the command is already loaded, use it directly.
+ klass = app_name
+ else:
+ klass = load_command_class(app_name, name)
+ except KeyError:
+ raise CommandError("Unknown command: %r" % name)
+ return klass
+
+
+ def execute(self, *args, **options):
+ """
+ Try to execute this command, performing model validation if
+ needed (as controlled by the attribute
+ ``self.requires_model_validation``). If the command raises a
+ ``CommandError``, intercept it and print it sensibly to
+ stderr.
+ """
+ show_traceback = options.get('traceback', False)
+
+ if self.can_import_settings:
+ try:
+ #language part used to be here
+ pass
+ except ImportError, e:
+ # If settings should be available, but aren't,
+ # raise the error and quit.
+ if show_traceback:
+ traceback.print_exc()
+ else:
+ sys.stderr.write(smart_str(self.style.ERROR('Error: %s\n' % e)))
+ sys.exit(1)
+
+ try:
+ self.stdout = options.get('stdout', sys.stdout)
+ self.stderr = options.get('stderr', sys.stderr)
+ if self.requires_model_validation:
+ self.validate()
+ output = self.handle(*args, **options)
+ if output:
+ if self.output_transaction:
+ # This needs to be imported here, because it relies on
+ # settings.
+ from django.db import connections, DEFAULT_DB_ALIAS
+ connection = connections[options.get('database', DEFAULT_DB_ALIAS)]
+ if connection.ops.start_transaction_sql():
+ self.stdout.write(self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()) + '\n')
+ self.stdout.write(output)
+ if self.output_transaction:
+ self.stdout.write('\n' + self.style.SQL_KEYWORD("COMMIT;") + '\n')
+ except CommandError, e:
+ if show_traceback:
+ traceback.print_exc()
+ else:
+ self.stderr.write(smart_str(self.style.ERROR('Error: %s\n' % e)))
+ sys.exit(1)
diff --git a/askbot/management/commands/askbot_update_index.py b/askbot/management/commands/askbot_update_index.py
new file mode 100644
index 00000000..6f55566b
--- /dev/null
+++ b/askbot/management/commands/askbot_update_index.py
@@ -0,0 +1,86 @@
+import sys
+from optparse import make_option
+
+from django.core.management import get_commands, load_command_class
+from django.utils.translation import activate as activate_language
+from django.core.management.base import BaseCommand, CommandError
+from django.conf import settings
+
+try:
+ from haystack.management.commands.update_index import Command as UpdateCommand
+ haystack_option_list = [option for option in UpdateCommand.base_options if option.get_opt_string() != '--verbosity']
+except ImportError:
+ haystack_option_list = []
+
+class Command(BaseCommand):
+ help = "Completely rebuilds the search index by removing the old data and then updating."
+ base_options = [make_option("-l", "--language", action="store", type="string", dest="language",
+ help='Language to user, in language code format'),]
+ option_list = list(BaseCommand.option_list) + haystack_option_list + base_options
+
+ def handle(self, *args, **options):
+ lang_code = options.get('language', settings.LANGUAGE_CODE.lower())
+ activate_language(lang_code)
+ options['using'] = ['default_%s' % lang_code[:2],]
+ klass = self._get_command_class('update_index')
+ klass.handle(*args, **options)
+
+ def _get_command_class(self, name):
+ try:
+ app_name = get_commands()[name]
+ if isinstance(app_name, BaseCommand):
+ # If the command is already loaded, use it directly.
+ klass = app_name
+ else:
+ klass = load_command_class(app_name, name)
+ except KeyError:
+ raise CommandError("Unknown command: %r" % name)
+ return klass
+
+
+ def execute(self, *args, **options):
+ """
+ Try to execute this command, performing model validation if
+ needed (as controlled by the attribute
+ ``self.requires_model_validation``). If the command raises a
+ ``CommandError``, intercept it and print it sensibly to
+ stderr.
+ """
+ show_traceback = options.get('traceback', False)
+
+ if self.can_import_settings:
+ try:
+ #language part used to be here
+ pass
+ except ImportError, e:
+ # If settings should be available, but aren't,
+ # raise the error and quit.
+ if show_traceback:
+ traceback.print_exc()
+ else:
+ sys.stderr.write(smart_str(self.style.ERROR('Error: %s\n' % e)))
+ sys.exit(1)
+
+ try:
+ self.stdout = options.get('stdout', sys.stdout)
+ self.stderr = options.get('stderr', sys.stderr)
+ if self.requires_model_validation:
+ self.validate()
+ output = self.handle(*args, **options)
+ if output:
+ if self.output_transaction:
+ # This needs to be imported here, because it relies on
+ # settings.
+ from django.db import connections, DEFAULT_DB_ALIAS
+ connection = connections[options.get('database', DEFAULT_DB_ALIAS)]
+ if connection.ops.start_transaction_sql():
+ self.stdout.write(self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()) + '\n')
+ self.stdout.write(output)
+ if self.output_transaction:
+ self.stdout.write('\n' + self.style.SQL_KEYWORD("COMMIT;") + '\n')
+ except CommandError, e:
+ if show_traceback:
+ traceback.print_exc()
+ else:
+ self.stderr.write(smart_str(self.style.ERROR('Error: %s\n' % e)))
+ sys.exit(1)
diff --git a/askbot/models/question.py b/askbot/models/question.py
index 5f56e583..8637ea23 100644
--- a/askbot/models/question.py
+++ b/askbot/models/question.py
@@ -48,24 +48,30 @@ class ThreadQuerySet(models.query.QuerySet):
todo: possibly add tags
todo: implement full text search on relevant fields
"""
- db_engine_name = askbot.get_database_engine_name()
- filter_parameters = {'deleted': False}
- if 'postgresql_psycopg2' in db_engine_name:
- from askbot.search import postgresql
- return postgresql.run_title_search(
- self, search_query
- ).filter(
- **filter_parameters
- ).order_by('-relevance')
- elif 'mysql' in db_engine_name and mysql.supports_full_text_search():
- filter_parameters['title__search'] = search_query
+
+ if getattr(django_settings, 'ENABLE_HAYSTACK_SEARCH', False):
+ from askbot.search.haystack.searchquery import AskbotSearchQuerySet
+ hs_qs = AskbotSearchQuerySet().filter(content=search_query).models(self.model)
+ return hs_qs.get_django_queryset()
else:
- filter_parameters['title__icontains'] = search_query
+ db_engine_name = askbot.get_database_engine_name()
+ filter_parameters = {'deleted': False}
+ if 'postgresql_psycopg2' in db_engine_name:
+ from askbot.search import postgresql
+ return postgresql.run_title_search(
+ self, search_query
+ ).filter(
+ **filter_parameters
+ ).order_by('-relevance')
+ elif 'mysql' in db_engine_name and mysql.supports_full_text_search():
+ filter_parameters['title__search'] = search_query
+ else:
+ filter_parameters['title__icontains'] = search_query
- if getattr(django_settings, 'ASKBOT_MULTILINGUAL', False):
- filter_parameters['language_code'] = get_language()
+ if getattr(django_settings, 'ASKBOT_MULTILINGUAL', False):
+ filter_parameters['language_code'] = get_language()
- return self.filter(**filter_parameters)
+ return self.filter(**filter_parameters)
class ThreadManager(BaseQuerySetManager):
@@ -207,7 +213,7 @@ class ThreadManager(BaseQuerySetManager):
todo: move to query set
"""
if getattr(django_settings, 'ENABLE_HAYSTACK_SEARCH', False):
- from askbot.search.haystack import AskbotSearchQuerySet
+ from askbot.search.haystack.searchquery import AskbotSearchQuerySet
hs_qs = AskbotSearchQuerySet().filter(content=search_query)
return hs_qs.get_django_queryset()
else:
diff --git a/askbot/search/haystack/__init__.py b/askbot/search/haystack/__init__.py
index 71f04d00..9b99ef28 100644
--- a/askbot/search/haystack/__init__.py
+++ b/askbot/search/haystack/__init__.py
@@ -1,59 +1,57 @@
-try:
- from haystack import indexes, site
- from haystack.query import SearchQuerySet
- from askbot.models import Post, Thread, User
-
-
- class ThreadIndex(indexes.SearchIndex):
- text = indexes.CharField(document=True, use_template=True)
- title = indexes.CharField(model_attr='title')
- post_text = indexes.CharField(model_attr='posts__text__search')
-
- def index_queryset(self):
- return Thread.objects.filter(posts__deleted=False)
-
- def prepare(self, obj):
- self.prepared_data = super(ThreadIndex, self).prepare(object)
-
- self.prepared_data['tags'] = [tag.name for tag in objects.tags.all()]
-
- class PostIndex(indexes.SearchIndex):
- text = indexes.CharField(document=True, use_template=True)
- post_text = indexes.CharField(model_attr='text')
- author = indexes.CharField(model_attr='user')
- thread_id = indexes.CharField(model_attr='thread')
-
- def index_queryset(self):
- return Post.objects.filter(deleted=False)
+from django.conf import settings
+from django.utils.translation import get_language
- class UserIndex(indexes.SearchIndex):
- text = indexes.CharField(document=True, use_template=True)
-
- def index_queryset(self):
- return User.objects.all()
-
- site.register(Post, PostIndex)
- site.register(Thread, ThreadIndex)
- site.register(User, UserIndex)
-
- class AskbotSearchQuerySet(SearchQuerySet):
-
- def get_django_queryset(self, model_klass=Thread):
- '''dirty hack because models() method from the
- SearchQuerySet does not work </3'''
- id_list = []
- for r in self:
- if r.model_name in ['thread','post'] \
- and model_klass._meta.object_name.lower() == 'thread':
- if getattr(r, 'thread_id'):
- id_list.append(r.thread_id)
- else:
- id_list.append(r.pk)
- elif r.model_name == model_klass._meta.object_name.lower():
- #FIXME: add a highlight here?
- id_list.append(r.pk)
-
- return model_klass.objects.filter(id__in=set(id_list))
+from haystack import indexes
+try:
+ from searchquery import AskbotSearchQuerySet
except:
pass
+
+class ThreadIndex(indexes.SearchIndex, indexes.Indexable):
+ text = indexes.CharField(document=True, use_template=True)
+ title = indexes.CharField()
+ tags = indexes.MultiValueField()
+
+ def get_model(self):
+ from askbot.models import Thread
+ return Thread
+
+ def index_queryset(self, using=None):
+ if getattr(settings, 'ASKBOT_MULTILINGUAL', True):
+ lang_code = get_language()[:2]
+ return self.get_model().objects.filter(language_code__startswith=lang_code,
+ deleted=False)
+ else:
+ return self.get_model().objects.filter(deleted=False)
+
+ def prepare_tags(self, obj):
+ return [tag.name for tag in obj.tags.all()]
+
+class PostIndex(indexes.SearchIndex, indexes.Indexable):
+ text = indexes.CharField(document=True, use_template=True)
+ post_text = indexes.CharField(model_attr='text')
+ author = indexes.CharField()
+ thread_id = indexes.IntegerField(model_attr='thread__pk')
+
+ def get_model(self):
+ from askbot.models import Post
+ return Post
+
+ def index_queryset(self, using=None):
+ if getattr(settings, 'ASKBOT_MULTILINGUAL', True):
+ lang_code = get_language()[:2]
+ return self.get_model().objects.filter(language_code__startswith=lang_code,
+ deleted=False)
+ else:
+ return self.get_model().objects.filter(deleted=False)
+
+class UserIndex(indexes.SearchIndex, indexes.Indexable):
+ text = indexes.CharField(document=True, use_template=True)
+
+ def get_model(self):
+ from askbot.models import User
+ return User
+
+ def index_queryset(self, using=None):
+ return self.get_model().objects.all()
diff --git a/askbot/search/haystack/routers.py b/askbot/search/haystack/routers.py
new file mode 100644
index 00000000..7b7e0a4b
--- /dev/null
+++ b/askbot/search/haystack/routers.py
@@ -0,0 +1,20 @@
+from django.utils.translation import get_language
+
+from haystack.routers import BaseRouter
+from haystack.constants import DEFAULT_ALIAS
+
+class LanguageRouter(BaseRouter):
+
+ def for_read(self, **hints):
+ from django.conf import settings
+ if getattr(settings, 'ASKBOT_MULTILINGUAL'):
+ return 'default_' + get_language()[:2]
+ else:
+ return DEFAULT_ALIAS
+
+ def for_write(self, **hints):
+ from django.conf import settings
+ if getattr(settings, 'ASKBOT_MULTILINGUAL'):
+ return 'default_' + get_language()[:2]
+ else:
+ return DEFAULT_ALIAS
diff --git a/askbot/search/haystack/searchquery.py b/askbot/search/haystack/searchquery.py
new file mode 100644
index 00000000..887936dc
--- /dev/null
+++ b/askbot/search/haystack/searchquery.py
@@ -0,0 +1,53 @@
+from askbot.models import Thread, User
+from haystack.query import SearchQuerySet
+
+class AskbotSearchQuerySet(SearchQuerySet):
+
+ def _determine_backend(self):
+ '''This is a hack somehow connection_router got wrong values
+ from setting and did not loaded the LanguageRouter'''
+
+ from haystack import connections, connection_router
+ # A backend has been manually selected. Use it instead.
+ if self._using is not None:
+ self.query = connections[self._using].get_query()
+ return
+
+ # No backend, so rely on the routers to figure out what's right.
+ hints = {}
+
+ if self.query:
+ hints['models'] = self.query.models
+
+ backend_alias = connection_router.for_read(**hints)
+
+ if isinstance(backend_alias, (list, tuple)) and len(backend_alias):
+ # We can only effectively read from one engine.
+ backend_alias = backend_alias[0]
+
+ # The ``SearchQuery`` might swap itself out for a different variant
+ # here.
+ if self.query:
+ self.query = self.query.using(backend_alias)
+ else:
+ self.query = connections[backend_alias].get_query()
+
+ def get_django_queryset(self, model_klass=Thread):
+ '''dirty hack because models() method from the
+ SearchQuerySet does not work </3'''
+ id_list = []
+ for r in self:
+ if r.model_name in ['thread','post'] \
+ and model_klass._meta.object_name.lower() == 'thread':
+ if getattr(r, 'thread_id'):
+ id_list.append(r.thread_id)
+ else:
+ id_list.append(r.pk)
+ elif r.model_name == model_klass._meta.object_name.lower():
+ #FIXME: add a highlight here?
+ id_list.append(r.pk)
+
+ if model_klass == User:
+ return model_klass.objects.filter(id__in=set(id_list))
+ else:
+ return model_klass.objects.filter(id__in=set(id_list), deleted=False)
diff --git a/askbot/search/haystack/signals.py b/askbot/search/haystack/signals.py
new file mode 100644
index 00000000..3f77ed13
--- /dev/null
+++ b/askbot/search/haystack/signals.py
@@ -0,0 +1,75 @@
+from django.db.models import signals as django_signals
+from django.contrib.auth.models import User
+
+from haystack.signals import BaseSignalProcessor
+
+class AskbotRealtimeSignalProcessor(BaseSignalProcessor):
+ '''
+ Based on haystack RealTimeSignalProcessor with some
+ modifications to work with askbot soft-delete models
+ '''
+
+ def setup(self):
+ django_signals.post_save.connect(self.handle_save)
+ django_signals.post_delete.connect(self.handle_delete)
+
+ try:
+ from askbot.models import signals as askbot_signals
+ askbot_signals.delete_question_or_answer.connect(self.handle_delete)
+ except ImportError:
+ pass
+
+ def teardown(self):
+ django_signals.post_save.disconnect(self.handle_save)
+ django_signals.post_delete.disconnect(self.handle_delete)
+ #askbot signals
+ try:
+ from askbot.models import signals as askbot_signals
+ askbot_signals.delete_question_or_answer.disconnect(self.handle_delete)
+ except ImportError:
+ pass
+
+try:
+ from haystack.exceptions import NotHandled
+ from celery_haystack.signals import CelerySignalProcessor
+ from celery_haystack.utils import enqueue_task
+
+ class AskbotCelerySignalProcessor(CelerySignalProcessor):
+
+ def setup(self):
+ django_signals.post_save.connect(self.enqueue_save)
+ django_signals.post_delete.connect(self.enqueue_delete)
+ try:
+ from askbot.models import signals as askbot_signals
+ askbot_signals.delete_question_or_answer.connect(self.enqueue_delete)
+ except ImportError:
+ pass
+
+
+ def teardown(self):
+ django_signals.post_save.disconnect(self.enqueue_save)
+ django_signals.post_delete.disconnect(self.enqueue_delete)
+
+ try:
+ from askbot.models import signals as askbot_signals
+ askbot_signals.delete_question_or_answer.disconnect(self.enqueue_delete)
+ except ImportError:
+ pass
+
+ def enqueue(self, action, instance, sender, **kwargs):
+ using_backends = self.connection_router.for_write(instance=instance)
+
+ for using in using_backends:
+ try:
+ connection = self.connections[using]
+ index = connection.get_unified_index().get_index(sender)
+ except NotHandled:
+ continue # Check next backend
+
+ if action == 'update' and not index.should_update(instance):
+ continue
+ enqueue_task(action, instance)
+ return # Only enqueue instance once
+
+except ImportError:
+ pass
diff --git a/askbot/search_indexes.py b/askbot/search_indexes.py
new file mode 100644
index 00000000..f2674996
--- /dev/null
+++ b/askbot/search_indexes.py
@@ -0,0 +1,4 @@
+from django.conf import settings
+
+if getattr(settings, 'ENABLE_HAYSTACK_SEARCH'):
+ from askbot.search.haystack import UserIndex, ThreadIndex, PostIndex
diff --git a/askbot/setup_templates/settings.py b/askbot/setup_templates/settings.py
index a462d2cf..da38a3f1 100644
--- a/askbot/setup_templates/settings.py
+++ b/askbot/setup_templates/settings.py
@@ -257,10 +257,16 @@ RECAPTCHA_USE_SSL = True
#HAYSTACK_SETTINGS
ENABLE_HAYSTACK_SEARCH = False
-HAYSTACK_SITECONF = 'askbot.search.haystack'
-#more information
-#http://django-haystack.readthedocs.org/en/v1.2.7/settings.html
-HAYSTACK_SEARCH_ENGINE = 'simple'
+#Uncomment for multilingual setup:
+#HAYSTACK_ROUTERS = ['askbot.search.haystack.routers.LanguageRouter',]
+
+#Uncomment if you use haystack
+#More info in http://django-haystack.readthedocs.org/en/latest/settings.html
+#HAYSTACK_CONNECTIONS = {
+# 'default': {
+# 'ENGINE': 'haystack.backends.simple_backend.SimpleEngine',
+# }
+#}
TINYMCE_COMPRESSOR = True
TINYMCE_SPELLCHECKER = False
diff --git a/askbot/setup_templates/settings.py.mustache b/askbot/setup_templates/settings.py.mustache
index 582e8392..7e617fc7 100644
--- a/askbot/setup_templates/settings.py.mustache
+++ b/askbot/setup_templates/settings.py.mustache
@@ -255,10 +255,17 @@ RECAPTCHA_USE_SSL = True
#HAYSTACK_SETTINGS
ENABLE_HAYSTACK_SEARCH = False
-HAYSTACK_SITECONF = 'askbot.search.haystack'
-#more information
-#http://django-haystack.readthedocs.org/en/v1.2.7/settings.html
-HAYSTACK_SEARCH_ENGINE = 'simple'
+#Uncomment for multilingual setup:
+#HAYSTACK_ROUTERS = ['askbot.search.haystack.routers.LanguageRouter',]
+
+#Uncomment if you use haystack
+#More info in http://django-haystack.readthedocs.org/en/latest/settings.html
+#HAYSTACK_CONNECTIONS = {
+# 'default': {
+# 'ENGINE': 'haystack.backends.simple_backend.SimpleEngine',
+# }
+#}
+
TINYMCE_COMPRESSOR = True
TINYMCE_SPELLCHECKER = False
diff --git a/askbot/startup_procedures.py b/askbot/startup_procedures.py
index 221fffaf..b25cdf56 100644
--- a/askbot/startup_procedures.py
+++ b/askbot/startup_procedures.py
@@ -625,13 +625,29 @@ def test_haystack():
try_import('haystack', 'django-haystack', short_message = True)
if getattr(django_settings, 'ENABLE_HAYSTACK_SEARCH', False):
errors = list()
- if not hasattr(django_settings, 'HAYSTACK_SEARCH_ENGINE'):
- message = "Please HAYSTACK_SEARCH_ENGINE to an appropriate value, value 'simple' can be used for basic testing"
+ if not hasattr(django_settings, 'HAYSTACK_CONNECTIONS'):
+ message = "Please HAYSTACK_CONNECTIONS to an appropriate value, value 'simple' can be used for basic testing sample:\n"
+ message += """HAYSTACK_CONNECTIONS = {
+ 'default': {
+ 'ENGINE': 'haystack.backends.simple_backend.SimpleEngine',
+ }
+ }"""
errors.append(message)
- if not hasattr(django_settings, 'HAYSTACK_SITECONF'):
- message = 'Please add HAYSTACK_SITECONF = "askbot.search.haystack"'
- errors.append(message)
- footer = 'Please refer to haystack documentation at http://django-haystack.readthedocs.org/en/v1.2.7/settings.html#haystack-search-engine'
+
+ if getattr(django_settings, 'ASKBOT_MULTILINGUAL'):
+ if not hasattr(django_settings, "HAYSTACK_ROUTERS"):
+ message = "Please add HAYSTACK_ROUTERS = ['askbot.search.haystack.routers.LanguageRouter',] to settings.py"
+ errors.append(message)
+ elif 'askbot.search.haystack.routers.LanguageRouter' not in \
+ getattr(django_settings, 'HAYSTACK_ROUTERS'):
+ message = "'askbot.search.haystack.routers.LanguageRouter' to HAYSTACK_ROUTERS as first element in settings.py"
+ errors.append(message)
+
+ if getattr(django_settings, 'HAYSTACK_SIGNAL_PROCESSOR',
+ '').endswith('AskbotCelerySignalProcessor'):
+ try_import('celery_haystack', 'celery-haystack', short_message = True)
+
+ footer = 'Please refer to haystack documentation at https://django-haystack.readthedocs.org/en/latest/settings.html'
print_errors(errors, footer=footer)
def test_custom_user_profile_tab():
@@ -974,10 +990,6 @@ def run_startup_tests():
'value': True,
'message': 'Please add: RECAPTCHA_USE_SSL = True'
},
- 'HAYSTACK_SITECONF': {
- 'value': 'askbot.search.haystack',
- 'message': 'Please add: HAYSTACK_SITECONF = "askbot.search.haystack"'
- }
})
settings_tester.run()
if 'manage.py test' in ' '.join(sys.argv):
diff --git a/askbot/templates/search/indexes/askbot/post_text.txt b/askbot/templates/search/indexes/askbot/post_text.txt
new file mode 100644
index 00000000..b373ccf7
--- /dev/null
+++ b/askbot/templates/search/indexes/askbot/post_text.txt
@@ -0,0 +1 @@
+{{ object.text }}
diff --git a/askbot/templates/search/indexes/askbot/thread_text.txt b/askbot/templates/search/indexes/askbot/thread_text.txt
new file mode 100644
index 00000000..0db5dba0
--- /dev/null
+++ b/askbot/templates/search/indexes/askbot/thread_text.txt
@@ -0,0 +1,6 @@
+{{ object.title }}
+{{ object.tags }}
+
+{% for tag in object.tags.all() %}
+{{ tag.name }}
+{% endfor %}
diff --git a/askbot/templates/search/indexes/auth/user_text.txt b/askbot/templates/search/indexes/auth/user_text.txt
new file mode 100644
index 00000000..b0baa3a6
--- /dev/null
+++ b/askbot/templates/search/indexes/auth/user_text.txt
@@ -0,0 +1,5 @@
+{{ object.username }}
+{{ object.first_name }}
+{{ object.last_name }}
+{{ object.email }}
+{{ object.full_name }}
diff --git a/askbot/templates/search_configuration/askbotsolr.xml b/askbot/templates/search_configuration/askbotsolr.xml
new file mode 100644
index 00000000..2605d58b
--- /dev/null
+++ b/askbot/templates/search_configuration/askbotsolr.xml
@@ -0,0 +1,209 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="default" version="1.4">
+ <types>
+ <fieldtype name="string" class="solr.StrField" sortMissingLast="true" omitNorms="true"/>
+ <fieldType name="boolean" class="solr.BoolField" sortMissingLast="true" omitNorms="true"/>
+ <fieldtype name="binary" class="solr.BinaryField"/>
+
+ <!-- Numeric field types that manipulate the value into
+ a string value that isn't human-readable in its internal form,
+ but with a lexicographic ordering the same as the numeric ordering,
+ so that range queries work correctly. -->
+ <fieldType name="int" class="solr.TrieIntField" precisionStep="0" omitNorms="true" sortMissingLast="true" positionIncrementGap="0"/>
+ <fieldType name="float" class="solr.TrieFloatField" precisionStep="0" omitNorms="true" sortMissingLast="true" positionIncrementGap="0"/>
+ <fieldType name="long" class="solr.TrieLongField" precisionStep="0" omitNorms="true" sortMissingLast="true" positionIncrementGap="0"/>
+ <fieldType name="double" class="solr.TrieDoubleField" precisionStep="0" omitNorms="true" sortMissingLast="true" positionIncrementGap="0"/>
+
+ <fieldType name="tint" class="solr.TrieIntField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
+ <fieldType name="tfloat" class="solr.TrieFloatField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
+ <fieldType name="tlong" class="solr.TrieLongField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
+ <fieldType name="tdouble" class="solr.TrieDoubleField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
+
+ <fieldType name="date" class="solr.TrieDateField" omitNorms="true" precisionStep="0" positionIncrementGap="0"/>
+ <!-- A Trie based date field for faster date range queries and date faceting. -->
+ <fieldType name="tdate" class="solr.TrieDateField" omitNorms="true" precisionStep="6" positionIncrementGap="0"/>
+
+ <fieldType name="point" class="solr.PointType" dimension="2" subFieldSuffix="_d"/>
+ <fieldType name="location" class="solr.LatLonType" subFieldSuffix="_coordinate"/>
+ <fieldtype name="geohash" class="solr.GeoHashField"/>
+
+ <fieldType name="text_general" class="solr.TextField" positionIncrementGap="100">
+ <analyzer type="index">
+ {% if language == 'en' %}
+ <tokenizer class="solr.StandardTokenizerFactory"/>
+ <filter class="solr.LowerCaseFilterFactory"/>
+ <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" enablePositionIncrements="true" />
+ {% elif language == 'es' %}
+ <filter class="solr.LowerCaseFilterFactory"/>
+ <filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_es.txt" enablePositionIncrements="true" />
+ <filter class="solr.SnowballPorterFilterFactory" language="Spanish" />
+ {% elif language == 'es' %}
+ <filter class="solr.LowerCaseFilterFactory"/>
+ <filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_ru.txt" enablePositionIncrements="true" />
+ <filter class="solr.SnowballPorterFilterFactory" language="Russian" />
+ {% elif language == 'fr' %}
+ <filter class="solr.LowerCaseFilterFactory"/>
+ <filter class="solr.ElisionFilterFactory"/>
+ <filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_fr.txt" enablePositionIncrements="true" />
+ <filter class="solr.SnowballPorterFilterFactory" language="French" />
+ {% elif language == 'cn' %}
+ <tokenizer class="solr.SmartChineseSentenceTokenizerFactory"/>
+ <filter class="solr.SmartChineseWordTokenFilterFactory"/>
+ <filter class="solr.LowerCaseFilterFactory"/>
+ <filter class="solr.PositionFilterFactory" />
+ {% elif language in ('jp', 'ko') %}
+ <tokenizer class="solr.CJKTokenizerFactory"/>
+ {% endif %}
+ </analyzer>
+ <analyzer type="query">
+ {% if language == 'en' %}
+ <tokenizer class="solr.StandardTokenizerFactory"/>
+ <filter class="solr.LowerCaseFilterFactory"/>
+ <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" enablePositionIncrements="true" />
+ {% elif language == 'es' %}
+ <filter class="solr.LowerCaseFilterFactory"/>
+ <filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_es.txt" enablePositionIncrements="true" />
+ <filter class="solr.SnowballPorterFilterFactory" language="Spanish" />
+ {% elif language == 'de' %}
+ <filter class="solr.LowerCaseFilterFactory"/>
+ <filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_de.txt" enablePositionIncrements="true" />
+ <filter class="solr.SnowballPorterFilterFactory" language="German2" />
+ {% elif language == 'ru' %}
+ <filter class="solr.LowerCaseFilterFactory"/>
+ <filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_ru.txt" enablePositionIncrements="true" />
+ <filter class="solr.SnowballPorterFilterFactory" language="Russian" />
+ {% elif language == 'fr' %}
+ <filter class="solr.LowerCaseFilterFactory"/>
+ <filter class="solr.ElisionFilterFactory"/>
+ <filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_fr.txt" enablePositionIncrements="true" />
+ <filter class="solr.SnowballPorterFilterFactory" language="French" />
+ {% elif language == 'cn' or language == 'zh' %}
+ <tokenizer class="solr.SmartChineseSentenceTokenizerFactory"/>
+ <filter class="solr.SmartChineseWordTokenFilterFactory"/>
+ <filter class="solr.LowerCaseFilterFactory"/>
+ <filter class="solr.PositionFilterFactory" />
+ {% elif language in ('jp', 'ko') %}
+ <tokenizer class="solr.CJKTokenizerFactory"/>
+ {% else %}
+ <tokenizer class="solr.StandardTokenizerFactory"/>
+ <filter class="solr.LowerCaseFilterFactory"/>
+ <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" enablePositionIncrements="true" />
+ {% endif %}
+ </analyzer>
+ </fieldType>
+
+ <fieldType name="text_en" class="solr.TextField" positionIncrementGap="100">
+ <analyzer type="index">
+ <tokenizer class="solr.StandardTokenizerFactory"/>
+ <filter class="solr.StopFilterFactory"
+ ignoreCase="true"
+ words="stopwords_en.txt"
+ enablePositionIncrements="true"
+ />
+ <filter class="solr.LowerCaseFilterFactory"/>
+ <filter class="solr.EnglishPossessiveFilterFactory"/>
+ <filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
+ <!-- Optionally you may want to use this less aggressive stemmer instead of PorterStemFilterFactory:
+ <filter class="solr.EnglishMinimalStemFilterFactory"/>
+ -->
+ <filter class="solr.PorterStemFilterFactory"/>
+ </analyzer>
+ <analyzer type="query">
+ <tokenizer class="solr.StandardTokenizerFactory"/>
+ <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
+ <filter class="solr.StopFilterFactory"
+ ignoreCase="true"
+ words="stopwords_en.txt"
+ enablePositionIncrements="true"
+ />
+ <filter class="solr.LowerCaseFilterFactory"/>
+ <filter class="solr.EnglishPossessiveFilterFactory"/>
+ <filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
+ <!-- Optionally you may want to use this less aggressive stemmer instead of PorterStemFilterFactory:
+ <filter class="solr.EnglishMinimalStemFilterFactory"/>
+ -->
+ <filter class="solr.PorterStemFilterFactory"/>
+ </analyzer>
+ </fieldType>
+
+ <fieldType name="text_ws" class="solr.TextField" positionIncrementGap="100">
+ <analyzer>
+ <tokenizer class="solr.WhitespaceTokenizerFactory"/>
+ </analyzer>
+ </fieldType>
+
+ <fieldType name="ngram" class="solr.TextField" >
+ <analyzer type="index">
+ <tokenizer class="solr.KeywordTokenizerFactory"/>
+ <filter class="solr.LowerCaseFilterFactory"/>
+ <filter class="solr.NGramFilterFactory" minGramSize="3" maxGramSize="15" />
+ </analyzer>
+ <analyzer type="query">
+ <tokenizer class="solr.KeywordTokenizerFactory"/>
+ <filter class="solr.LowerCaseFilterFactory"/>
+ </analyzer>
+ </fieldType>
+
+ <fieldType name="edge_ngram" class="solr.TextField" positionIncrementGap="1">
+ <analyzer type="index">
+ <tokenizer class="solr.WhitespaceTokenizerFactory" />
+ <filter class="solr.LowerCaseFilterFactory" />
+ <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
+ <filter class="solr.EdgeNGramFilterFactory" minGramSize="2" maxGramSize="15" side="front" />
+ </analyzer>
+ <analyzer type="query">
+ <tokenizer class="solr.WhitespaceTokenizerFactory" />
+ <filter class="solr.LowerCaseFilterFactory" />
+ <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
+ </analyzer>
+ </fieldType>
+ </types>
+
+ <fields>
+ <!-- general -->
+ <field name="{{ ID }}" type="string" indexed="true" stored="true" multiValued="false" required="true"/>
+ <field name="{{ DJANGO_CT }}" type="string" indexed="true" stored="true" multiValued="false"/>
+ <field name="{{ DJANGO_ID }}" type="string" indexed="true" stored="true" multiValued="false"/>
+
+ <dynamicField name="*_i" type="int" indexed="true" stored="true"/>
+ <dynamicField name="*_s" type="string" indexed="true" stored="true"/>
+ <dynamicField name="*_l" type="long" indexed="true" stored="true"/>
+ <dynamicField name="*_t" type="text_en" indexed="true" stored="true"/>
+ <dynamicField name="*_b" type="boolean" indexed="true" stored="true"/>
+ <dynamicField name="*_f" type="float" indexed="true" stored="true"/>
+ <dynamicField name="*_d" type="double" indexed="true" stored="true"/>
+ <dynamicField name="*_dt" type="date" indexed="true" stored="true"/>
+ <dynamicField name="*_p" type="location" indexed="true" stored="true"/>
+ <dynamicField name="*_coordinate" type="tdouble" indexed="true" stored="false"/>
+
+{% for field in fields %}
+ <field name="{{ field.field_name }}" type="{{ field.type }}" indexed="{{ field.indexed }}" stored="{{ field.stored }}" multiValued="{{ field.multi_valued }}" />
+{% endfor %}
+ </fields>
+
+ <!-- field to use to determine and enforce document uniqueness. -->
+ <uniqueKey>{{ ID }}</uniqueKey>
+
+ <!-- field for the QueryParser to use when an explicit fieldname is absent -->
+ <defaultSearchField>{{ content_field_name }}</defaultSearchField>
+
+ <!-- SolrQueryParser configuration: defaultOperator="AND|OR" -->
+ <solrQueryParser defaultOperator="{{ default_operator }}"/>
+</schema>
diff --git a/askbot/tests/haystack_search_tests.py b/askbot/tests/haystack_search_tests.py
index 7a8bfcfd..c256c533 100644
--- a/askbot/tests/haystack_search_tests.py
+++ b/askbot/tests/haystack_search_tests.py
@@ -12,7 +12,9 @@ class HaystackSearchTests(AskbotTestCase):
"""
def setUp(self):
self._old_value = getattr(settings, 'ENABLE_HAYSTACK_SEARCH', False)
+ self._old_search_engine = getattr(settings, 'HAYSTACK_SEARCH_ENGINE', 'simple')
setattr(settings, "ENABLE_HAYSTACK_SEARCH", True)
+ setattr(settings, "HAYSTACK_SEARCH_ENGINE", 'simple')
self.user = self.create_user(username='gepeto')
self.other_user = self.create_user(username = 'pinocho')
@@ -53,6 +55,7 @@ class HaystackSearchTests(AskbotTestCase):
def tearDown(self):
setattr(settings, "ENABLE_HAYSTACK_SEARCH", self._old_value)
+ setattr(settings, "HAYSTACK_SEARCH_ENGINE", self._old_search_engine)
@skipIf('haystack' not in settings.INSTALLED_APPS,
'Haystack not setup')
diff --git a/askbot/urls.py b/askbot/urls.py
index 9ccd795d..07b701e0 100644
--- a/askbot/urls.py
+++ b/askbot/urls.py
@@ -607,3 +607,18 @@ if 'avatar' in settings.INSTALLED_APPS:
name='avatar_render_primary'
),
)
+
+
+#HACK: to register the haystack signals correclty due to import errors
+# http://i.qkme.me/3uuvs7.jpg
+if getattr(settings, 'HAYSTACK_SIGNAL_PROCESSOR',
+ ' ').endswith('AskbotRealtimeSignalProcessor'):
+ from haystack import signal_processor
+ signal_processor.teardown()
+ signal_processor.setup()
+
+if getattr(settings, 'HAYSTACK_SIGNAL_PROCESSOR',
+ ' ').endswith('AskbotCelerySignalProcessor'):
+ from haystack import signal_processor
+ signal_processor.teardown()
+ signal_processor.setup()
diff --git a/askbot/views/widgets.py b/askbot/views/widgets.py
index 4d7d02b2..5c4042fa 100644
--- a/askbot/views/widgets.py
+++ b/askbot/views/widgets.py
@@ -115,7 +115,8 @@ def ask_widget(request, widget_id):
return redirect('ask_by_widget_complete')
else:
#FIXME: this redirect is temporal need to create the correct view
- next_url = '%s?next=%s' % (reverse('widget_signin'), reverse('ask_by_widget'))
+ next_url = '%s?next=%s' % (reverse('widget_signin'),
+ reverse('ask_by_widget', args=(widget_id,)))
return redirect(next_url)
form = forms.AskWidgetForm(