[Python-modules-commits] [django-haystack] 01/04: Import django-haystack_2.5.1.orig.tar.gz

Michael Fladischer fladi at moszumanska.debian.org
Tue Jan 3 14:18:21 UTC 2017


This is an automated email from the git hooks/post-receive script.

fladi pushed a commit to branch master
in repository django-haystack.

commit 54f8cc3685c3806f4f590ec7c938979443c7bb9a
Author: Michael Fladischer <FladischerMichael at fladi.at>
Date:   Tue Jan 3 15:00:33 2017 +0100

    Import django-haystack_2.5.1.orig.tar.gz
---
 .gitignore                                         |  4 ++-
 .travis.yml                                        |  4 +--
 AUTHORS                                            |  2 ++
 README.rst                                         |  1 +
 docs/backend_support.rst                           |  2 +-
 docs/best_practices.rst                            |  6 ++--
 docs/changelog.rst                                 | 19 +++++-----
 docs/searchindex_api.rst                           | 20 +++++------
 docs/searchqueryset_api.rst                        | 25 +++++++++++--
 haystack/backends/__init__.py                      |  3 +-
 haystack/backends/elasticsearch_backend.py         |  6 ++--
 haystack/backends/solr_backend.py                  | 14 ++++++--
 haystack/backends/whoosh_backend.py                |  6 ++--
 haystack/constants.py                              |  3 +-
 haystack/management/commands/rebuild_index.py      |  4 +++
 haystack/management/commands/update_index.py       |  6 ++--
 haystack/utils/__init__.py                         |  5 ++-
 setup.py                                           |  4 +--
 .../test_elasticsearch_backend.py                  |  9 +++--
 .../test_elasticsearch_query.py                    | 10 ++++++
 test_haystack/settings.py                          |  4 +--
 test_haystack/solr_tests/test_solr_backend.py      | 26 ++++++++++----
 test_haystack/solr_tests/test_solr_query.py        | 11 +++++-
 test_haystack/test_managers.py                     |  4 +--
 test_haystack/test_query.py                        | 42 +++++++++++-----------
 test_haystack/test_views.py                        |  3 +-
 test_haystack/whoosh_tests/test_whoosh_backend.py  |  9 +++--
 test_haystack/whoosh_tests/test_whoosh_query.py    | 10 ++++++
 tox.ini                                            | 31 ++++++++++++++++
 29 files changed, 207 insertions(+), 86 deletions(-)

diff --git a/.gitignore b/.gitignore
index aef937e..219f74f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,8 +11,10 @@ MANIFEST
 env
 env3
 *.egg
+.eggs
 .coverage
 .idea
 
-test_haystack/solr_tests/server/solr-4.6.0.tgz
+# Build artifacts from test setup
+*.tgz
 test_haystack/solr_tests/server/solr4/
diff --git a/.travis.yml b/.travis.yml
index 5d62930..48dd983 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -28,6 +28,7 @@ before_install:
     - mkdir -p $HOME/download-cache
 
 install:
+    - pip install --upgrade setuptools
     - pip install requests "Django${DJANGO_VERSION}"
     - python setup.py clean build install
 
@@ -42,11 +43,10 @@ env:
     matrix:
         - DJANGO_VERSION=">=1.8,<1.9"
         - DJANGO_VERSION=">=1.9,<1.10"
-        - DJANGO_VERSION="==1.10a1"
+        - DJANGO_VERSION=">=1.10,<1.11"
 
 matrix:
     allow_failures:
-        - env: DJANGO_VERSION="==1.10a1"
         - python: 'pypy'
 
 services:
diff --git a/AUTHORS b/AUTHORS
index 69d86d0..7fdf08d 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -113,3 +113,5 @@ Thanks to
     * Gilad Beeri (@giladbeeri) for adding retries when updating a backend
     * Arjen Verstoep (@terr) for a patch that allows attribute lookups through Django ManyToManyField relationships
     * Tim Babych (@tymofij) for enabling backend-specific parameters in ``.highlight()``
+    * Antony Raj (@antonyr) for adding endswith input type and fixing contains input type
+    * Morgan Aubert (@ellmetha) for Django 1.10 support
diff --git a/README.rst b/README.rst
index d4ed215..d5959e2 100644
--- a/README.rst
+++ b/README.rst
@@ -33,6 +33,7 @@ Documentation
 =============
 
 * Development version: http://docs.haystacksearch.org/
+* v2.5.X: https://django-haystack.readthedocs.io/en/v2.5.0/
 * v2.4.X: https://django-haystack.readthedocs.io/en/v2.4.1/
 * v2.3.X: https://django-haystack.readthedocs.io/en/v2.3.0/
 * v2.2.X: https://django-haystack.readthedocs.io/en/v2.2.0/
diff --git a/docs/backend_support.rst b/docs/backend_support.rst
index 0b44441..a328c38 100644
--- a/docs/backend_support.rst
+++ b/docs/backend_support.rst
@@ -104,7 +104,7 @@ to develop a plugin following the lead of `xapian-haystack <https://pypi.python.
 that project can be developed and tested independently of the core Haystack release schedule.
 
 Sphinx
-~~~~~~
+------
 
 This backend has been requested multiple times over the years but does not yet have a volunteer maintainer. If
 you would like to work on it, please contact the Haystack maintainers so your project can be linked here and,
diff --git a/docs/best_practices.rst b/docs/best_practices.rst
index bf70eaf..b79f716 100644
--- a/docs/best_practices.rst
+++ b/docs/best_practices.rst
@@ -248,11 +248,13 @@ methods to call an enqueuing method instead of directly calling
 
         # Add on a queuing method.
         def enqueue_save(self, sender, instance, **kwargs):
-            # Push the save & information onto queue du jour here...
+            # Push the save & information onto queue du jour here
+            ...
 
         # Add on a queuing method.
         def enqueue_delete(self, sender, instance, **kwargs):
-            # Push the delete & information onto queue du jour here...
+            # Push the delete & information onto queue du jour here
+            ...
 
 For the consumer, this is much more specific to the queue used and your desired
 setup. At a minimum, you will need to periodically consume the queue, fetch the
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 9e079aa..5a5e7a1 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -679,7 +679,7 @@ v2.4.0 (2015-06-09)
 - App_loading cleanup. [Chris Adams]
 
   * Add support for Django 1.7+ AppConfig
-  * Rename internal app_loading functions to have haystack_ prefix to make
+  * Rename internal app_loading functions to have haystack\_ prefix to make
     it immediately obvious that they are not Django utilities and start
   * Add tests to avoid regressions for apps nested with multiple levels of
     module hierarchy like `raven.contrib.django.raven_compat`
@@ -1183,9 +1183,12 @@ v2.2.0 (2014-08-03)
   * Massively simplified test runner (``python setup.py test``)
 
   Minor updates:
+
   * Travis:
-      - Test Python 3.4
-      - Use Solr 4.6.1
+
+    - Test Python 3.4
+    - Use Solr 4.6.1
+
   * Simplified legacy test code which can now be replaced by the test utilities in newer versions of Django
   * Update ElasticSearch client & tests for ES 1.0+
   * Add option for SearchModelAdmin to specify the haystack connection to use
@@ -1590,10 +1593,8 @@ v2.2.0 (2014-08-03)
 
 - Merge pull request #413 from phill-tornroth/patch-1. [Justin Caratzas]
 
-  Silly little change, I know.. but I actually ran into a case where I acci
-
 - Silly little change, I know.. but I actually ran into a case where I
-  accidentally passed a list of models in without *ing them. When that
+  accidentally passed a list of models in without \*ing them. When that
   happens, we get a string formatting exception (not all arguments were
   formatted) instead of the useful "that ain't a model, kid" business.
   [Phill Tornroth]
@@ -3738,7 +3739,7 @@ v1.1 (2010-11-23)
 
 - We actually want `repr`, not `str`. [Daniel Lindsley]
 
-- Pushed the `model_attr` check lower down into the `SearchField`s and
+- Pushed the `model_attr` check lower down into the `SearchField` and
   make it occur later, so that exceptions come at a point where Django
   can better deal with them. [Daniel Lindsley]
 
@@ -3960,7 +3961,7 @@ v1.1 (2010-11-23)
   SmileyChris for patches. [Daniel Lindsley]
 
 - Added a note and an exception about consistent fieldnames for the
-  document field across all `SearchIndex` classes. Thanks sk1p_! [Daniel
+  document field across all `SearchIndex` classes. Thanks sk1p\_! [Daniel
   Lindsley]
 
 - Possible thread-safety fix related to registration handling. [Daniel
@@ -4303,7 +4304,7 @@ v1.1 (2010-11-23)
 - Corrected Xapian's capabilities. Thanks richardb! [Daniel Lindsley]
 
 - BACKWARD INCOMPATIBLE - Altered all settings to be prefixed with
-  HAYSTACK_. Thanks Collin! [Daniel Lindsley]
+  HAYSTACK\_. Thanks Collin! [Daniel Lindsley]
 
 - Test cleanup from previous commits. [Daniel Lindsley]
 
diff --git a/docs/searchindex_api.rst b/docs/searchindex_api.rst
index 8263f80..4dd2348 100644
--- a/docs/searchindex_api.rst
+++ b/docs/searchindex_api.rst
@@ -88,11 +88,11 @@ within which to primarily search. Because this ideal is so central and most of
 Haystack is designed to have pluggable backends, it is important to ensure that
 all engines have at least a bare minimum of the data they need to function.
 
-As a result, when creating a ``SearchIndex``, at least one field must be marked
-with ``document=True``. This signifies to Haystack that whatever is placed in
-this field while indexing is to be the primary text the search engine indexes.
-The name of this field can be almost anything, but ``text`` is one of the
-more common names used.
+As a result, when creating a ``SearchIndex``, one (and only one) field must be
+marked with ``document=True``. This signifies to Haystack that whatever is
+placed in this field while indexing is to be the primary text the search engine
+indexes. The name of this field can be almost anything, but ``text`` is one of
+the more common names used.
 
 
 Stored/Indexed Fields
@@ -111,12 +111,12 @@ them through one query instead of many. This still hits the DB and incurs a
 performance penalty.
 
 The other option is to leverage stored fields. By default, all fields in
-Haystack are both indexed (searchable by the engine) and stored (retained by
+Haystack are either indexed (searchable by the engine) or stored (retained by
 the engine and presented in the results). By using a stored field, you can
 store commonly used data in such a way that you don't need to hit the database
 when processing the search result to get more information.
 
-For example, one great way to leverage this is to pre-rendering an object's
+For example, one great way to leverage this is to pre-render an object's
 search result template DURING indexing. You define an additional field, render
 a template with it and it follows the main indexed record into the index. Then,
 when that record is pulled when it matches a query, you can simply display the
@@ -152,9 +152,9 @@ Keeping The Index Fresh
 =======================
 
 There are several approaches to keeping the search index in sync with your
-database. None are more correct than the others and depending the traffic you
-see, the churn rate of your data and what concerns are important to you
-(CPU load, how recent, et cetera).
+database. None are more correct than the others and which one you use depends
+on the traffic you see, the churn rate of your data, and what concerns are
+important to you (CPU load, how recent, et cetera).
 
 The conventional method is to use ``SearchIndex`` in combination with cron
 jobs. Running a ``./manage.py update_index`` every couple hours will keep your
diff --git a/docs/searchqueryset_api.rst b/docs/searchqueryset_api.rst
index 683bd8f..d4f29d2 100644
--- a/docs/searchqueryset_api.rst
+++ b/docs/searchqueryset_api.rst
@@ -235,6 +235,23 @@ a highlighted version of the result::
     result = sqs[0]
     result.highlighted['text'][0] # u'Two computer scientists walk into a bar. The bartender says "<em>Foo</em>!".'
 
+The default functionality of the highlighter may not suit your needs.
+You can pass additional keyword arguments to ``highlight`` that will
+ultimately be used to build the query for your backend. Depending on the
+available arguments for your backend, you may need to pass in a dictionary
+instead of normal keyword arguments::
+
+    # Solr defines the fields to higlight by the ``hl.fl`` param. If not specified, we
+    # would only get `text` back in the ``highlighted`` dict.
+    kwargs = {
+        'hl.fl': 'other_field',
+        'hl.simple.pre': '<span class="highlighted">',
+        'hl.simple.post': '</span>'
+    }
+    sqs = SearchQuerySet().filter(content='foo').highlight(**kwargs)
+    result = sqs[0]
+    result.highlighted['other_field'][0] # u'Two computer scientists walk into a bar. The bartender says "<span class="highlighted">Foo</span>!".'
+
 ``models``
 ~~~~~~~~~~
 
@@ -800,6 +817,7 @@ Field Lookups
 
 The following lookup types are supported:
 
+* content
 * contains
 * exact
 * gt
@@ -808,6 +826,7 @@ The following lookup types are supported:
 * lte
 * in
 * startswith
+* endswith
 * range
 * fuzzy
 
@@ -826,10 +845,10 @@ The actual behavior of these lookups is backend-specific.
 
 .. warning::
 
-    The ``contains`` filter became the new default filter as of Haystack v2.X
+    The ``content`` filter became the new default filter as of Haystack v2.X
     (the default in Haystack v1.X was ``exact``). This changed because ``exact``
     caused problems and was unintuitive for new people trying to use Haystack.
-    ``contains`` is a much more natural usage.
+    ``content`` is a much more natural usage.
 
     If you had an app built on Haystack v1.X & are upgrading, you'll need to
     sanity-check & possibly change any code that was relying on the default.
@@ -841,7 +860,7 @@ Example::
     SearchQuerySet().filter(content='foo')
 
     # Identical to:
-    SearchQuerySet().filter(content__contains='foo')
+    SearchQuerySet().filter(content__content='foo')
 
     # Phrase matching.
     SearchQuerySet().filter(content__exact='hello world')
diff --git a/haystack/backends/__init__.py b/haystack/backends/__init__.py
index f22e67a..e326d01 100644
--- a/haystack/backends/__init__.py
+++ b/haystack/backends/__init__.py
@@ -401,9 +401,8 @@ class SearchNode(tree.Node):
         """Parses an expression and determines the field and filter type."""
         parts = expression.split(FILTER_SEPARATOR)
         field = parts[0]
-
         if len(parts) == 1 or parts[-1] not in VALID_FILTERS:
-            filter_type = 'contains'
+            filter_type = 'content'
         else:
             filter_type = parts.pop()
 
diff --git a/haystack/backends/elasticsearch_backend.py b/haystack/backends/elasticsearch_backend.py
index cb3d765..c78dd47 100644
--- a/haystack/backends/elasticsearch_backend.py
+++ b/haystack/backends/elasticsearch_backend.py
@@ -802,7 +802,9 @@ class ElasticsearchSearchQuery(BaseSearchQuery):
             index_fieldname = u'%s:' % connections[self._using].get_unified_index().get_index_fieldname(field)
 
         filter_types = {
-            'contains': u'%s',
+            'content': u'%s',
+            'contains': u'*%s*',
+            'endswith': u'*%s',
             'startswith': u'%s*',
             'exact': u'%s',
             'gt': u'{%s TO *}',
@@ -815,7 +817,7 @@ class ElasticsearchSearchQuery(BaseSearchQuery):
         if value.post_process is False:
             query_frag = prepared_value
         else:
-            if filter_type in ['contains', 'startswith', 'fuzzy']:
+            if filter_type in ['content', 'contains', 'startswith', 'endswith', 'fuzzy']:
                 if value.input_type_name == 'exact':
                     query_frag = prepared_value
                 else:
diff --git a/haystack/backends/solr_backend.py b/haystack/backends/solr_backend.py
index e1f5ff1..533410e 100644
--- a/haystack/backends/solr_backend.py
+++ b/haystack/backends/solr_backend.py
@@ -189,7 +189,13 @@ class SolrSearchBackend(BaseSearchBackend):
             kwargs['hl.fragsize'] = '200'
 
             if isinstance(highlight, dict):
-                kwargs.update(highlight)
+                # autoprefix highlighter options with 'hl.', all of them start with it anyway
+                # this makes option dicts shorter: {'maxAnalyzedChars': 42}
+                # and lets some of options be used as keyword arguments: `.highlight(preserveMulti=False)`
+                kwargs.update({
+                    key if key.startswith("hl.") else ('hl.' + key): highlight[key] 
+                    for key in highlight.keys()
+                })
 
         if self.include_spelling is True:
             kwargs['spellcheck'] = 'true'
@@ -565,7 +571,9 @@ class SolrSearchQuery(BaseSearchQuery):
             index_fieldname = u'%s:' % connections[self._using].get_unified_index().get_index_fieldname(field)
 
         filter_types = {
-            'contains': u'%s',
+            'content': u'%s',
+            'contains': u'*%s*',
+            'endswith': u'*%s',
             'startswith': u'%s*',
             'exact': u'%s',
             'gt': u'{%s TO *}',
@@ -578,7 +586,7 @@ class SolrSearchQuery(BaseSearchQuery):
         if value.post_process is False:
             query_frag = prepared_value
         else:
-            if filter_type in ['contains', 'startswith', 'fuzzy']:
+            if filter_type in ['content', 'contains', 'startswith', 'endswith', 'fuzzy']:
                 if value.input_type_name == 'exact':
                     query_frag = prepared_value
                 else:
diff --git a/haystack/backends/whoosh_backend.py b/haystack/backends/whoosh_backend.py
index c610ce6..96f875b 100644
--- a/haystack/backends/whoosh_backend.py
+++ b/haystack/backends/whoosh_backend.py
@@ -813,7 +813,9 @@ class WhooshSearchQuery(BaseSearchQuery):
             index_fieldname = u'%s:' % connections[self._using].get_unified_index().get_index_fieldname(field)
 
         filter_types = {
-            'contains': '%s',
+            'content': '%s',
+            'contains': '*%s*',
+            'endswith': "*%s",
             'startswith': "%s*",
             'exact': '%s',
             'gt': "{%s to}",
@@ -826,7 +828,7 @@ class WhooshSearchQuery(BaseSearchQuery):
         if value.post_process is False:
             query_frag = prepared_value
         else:
-            if filter_type in ['contains', 'startswith', 'fuzzy']:
+            if filter_type in ['content', 'contains', 'startswith', 'endswith', 'fuzzy']:
                 if value.input_type_name == 'exact':
                     query_frag = prepared_value
                 else:
diff --git a/haystack/constants.py b/haystack/constants.py
index 85b2ea1..4e6f274 100644
--- a/haystack/constants.py
+++ b/haystack/constants.py
@@ -19,7 +19,8 @@ FUZZY_MIN_SIM = getattr(settings, 'HAYSTACK_FUZZY_MIN_SIM', 0.5)
 FUZZY_MAX_EXPANSIONS = getattr(settings, 'HAYSTACK_FUZZY_MAX_EXPANSIONS', 50)
 
 # Valid expression extensions.
-VALID_FILTERS = set(['contains', 'exact', 'gt', 'gte', 'lt', 'lte', 'in', 'startswith', 'range', 'fuzzy'])
+VALID_FILTERS = set(['contains', 'exact', 'gt', 'gte', 'lt', 'lte', 'in', 'startswith', 'range', 'endswith', 'content', 'fuzzy'])
+
 FILTER_SEPARATOR = '__'
 
 # The maximum number of items to display in a SearchQuerySet.__repr__
diff --git a/haystack/management/commands/rebuild_index.py b/haystack/management/commands/rebuild_index.py
index d8dac87..c197eb8 100644
--- a/haystack/management/commands/rebuild_index.py
+++ b/haystack/management/commands/rebuild_index.py
@@ -27,6 +27,10 @@ class Command(BaseCommand):
             '--nocommit', action='store_false', dest='commit',
             default=True, help='Will pass commit=False to the backend.'
         )
+        parser.add_argument(
+            '-b', '--batch-size', dest='batchsize', type=int,
+            help='Number of items to index at once.'
+        )
 
     def handle(self, **options):
         call_command('clear_index', **options)
diff --git a/haystack/management/commands/update_index.py b/haystack/management/commands/update_index.py
index c9d2550..8686dd7 100755
--- a/haystack/management/commands/update_index.py
+++ b/haystack/management/commands/update_index.py
@@ -42,10 +42,10 @@ def update_worker(args):
         if 'sqlite3' not in info['ENGINE']:
             try:
                 close_old_connections()
-                if isinstance(connections.thread_local.connections, dict):
-                    del connections.thread_local.connections[alias]
+                if isinstance(connections._connections, dict):
+                    del connections._connections[alias]
                 else:
-                    delattr(connections.thread_local.connections, alias)
+                    delattr(connections._connections, alias)
             except KeyError:
                 pass
 
diff --git a/haystack/utils/__init__.py b/haystack/utils/__init__.py
index 1f37052..3cfe1b0 100644
--- a/haystack/utils/__init__.py
+++ b/haystack/utils/__init__.py
@@ -64,7 +64,10 @@ get_identifier = _lookup_identifier_method()
 
 
 def get_model_ct_tuple(model):
-    return (model._meta.app_label, model._meta.model_name)
+    # Deferred models should be identified as if they were the underlying model.
+    model_name = model._meta.concrete_model._meta.model_name \
+        if hasattr(model, '_deferred') and model._deferred else model._meta.model_name
+    return (model._meta.app_label, model_name)
 
 def get_model_ct(model):
     return "%s.%s" % get_model_ct_tuple(model)
diff --git a/setup.py b/setup.py
index 69069d2..4261171 100755
--- a/setup.py
+++ b/setup.py
@@ -13,7 +13,7 @@ except ImportError:
 
 install_requires = [
     'Django>=1.8',
-    'Django<1.10',
+    'Django<1.11',
 ]
 
 tests_require = [
@@ -30,7 +30,7 @@ tests_require = [
 
 setup(
     name='django-haystack',
-    version='2.5.0',
+    version='2.5.1',
     description='Pluggable search for Django.',
     author='Daniel Lindsley',
     author_email='daniel at toastdriven.com',
diff --git a/test_haystack/elasticsearch_tests/test_elasticsearch_backend.py b/test_haystack/elasticsearch_tests/test_elasticsearch_backend.py
index f2e7087..d031aa6 100644
--- a/test_haystack/elasticsearch_tests/test_elasticsearch_backend.py
+++ b/test_haystack/elasticsearch_tests/test_elasticsearch_backend.py
@@ -851,7 +851,7 @@ class LiveElasticsearchSearchQuerySetTestCase(TestCase):
         # This will break horrifically if escaping isn't working.
         sqs = self.sqs.auto_query('"pants:rule"')
         self.assertTrue(isinstance(sqs, SearchQuerySet))
-        self.assertEqual(repr(sqs.query.query_filter), '<SQ: AND content__contains="pants:rule">')
+        self.assertEqual(repr(sqs.query.query_filter), '<SQ: AND content__content="pants:rule">')
         self.assertEqual(sqs.query.build_query(), u'("pants\\:rule")')
         self.assertEqual(len(sqs), 0)
 
@@ -1126,11 +1126,10 @@ class LiveElasticsearchMoreLikeThisTestCase(TestCase):
         if hasattr(MockModel.objects, 'defer'):
             # Make sure MLT works with deferred bits.
             mi = MockModel.objects.defer('foo').get(pk=1)
-            self.assertEqual(mi._deferred, True)
             deferred = self.sqs.models(MockModel).more_like_this(mi)
-            self.assertEqual(deferred.count(), 0)
-            self.assertEqual([result.pk for result in deferred], [])
-            self.assertEqual(len([result.pk for result in deferred]), 0)
+            self.assertEqual(deferred.count(), 4)
+            self.assertEqual(set([result.pk for result in deferred]), set([u'2', u'6', u'16', u'23']))
+            self.assertEqual(len([result.pk for result in deferred]), 4)
 
         # Ensure that swapping the ``result_class`` works.
         self.assertTrue(isinstance(self.sqs.result_class(MockSearchResult).more_like_this(MockModel.objects.get(pk=1))[0], MockSearchResult))
diff --git a/test_haystack/elasticsearch_tests/test_elasticsearch_query.py b/test_haystack/elasticsearch_tests/test_elasticsearch_query.py
index 5425f90..cdee5a0 100644
--- a/test_haystack/elasticsearch_tests/test_elasticsearch_query.py
+++ b/test_haystack/elasticsearch_tests/test_elasticsearch_query.py
@@ -122,6 +122,16 @@ class ElasticsearchSearchQueryTestCase(TestCase):
         self.sq.add_filter(SQ(title__fuzzy='haystack'))
         self.assertEqual(self.sq.build_query(), u'((why) AND title:(haystack~))')
 
+    def test_build_query_with_contains(self):
+        self.sq.add_filter(SQ(content='circular'))
+        self.sq.add_filter(SQ(title__contains='haystack'))
+        self.assertEqual(self.sq.build_query(), u'((circular) AND title:(*haystack*))')
+
+    def test_build_query_with_endswith(self):
+        self.sq.add_filter(SQ(content='circular'))
+        self.sq.add_filter(SQ(title__endswith='haystack'))
+        self.assertEqual(self.sq.build_query(), u'((circular) AND title:(*haystack))')
+
     def test_clean(self):
         self.assertEqual(self.sq.clean('hello world'), 'hello world')
         self.assertEqual(self.sq.clean('hello AND world'), 'hello and world')
diff --git a/test_haystack/settings.py b/test_haystack/settings.py
index d676c0d..43bf75f 100644
--- a/test_haystack/settings.py
+++ b/test_haystack/settings.py
@@ -80,7 +80,7 @@ HAYSTACK_CONNECTIONS = {
     },
     'elasticsearch': {
         'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
-        'URL': '127.0.0.1:9200/',
+        'URL': os.environ.get('TEST_ELASTICSEARCH_1_URL', 'http://localhost:9200/'),
         'INDEX_NAME': 'test_default',
         'INCLUDE_SPELLING': True,
     },
@@ -89,7 +89,7 @@ HAYSTACK_CONNECTIONS = {
     },
     'solr': {
         'ENGINE': 'haystack.backends.solr_backend.SolrEngine',
-        'URL': 'http://localhost:9001/solr/',
+        'URL': os.environ.get('TEST_SOLR_URL', 'http://localhost:9001/solr/'),
         'INCLUDE_SPELLING': True,
     },
 }
diff --git a/test_haystack/solr_tests/test_solr_backend.py b/test_haystack/solr_tests/test_solr_backend.py
index be6d9d8..a9fdda4 100644
--- a/test_haystack/solr_tests/test_solr_backend.py
+++ b/test_haystack/solr_tests/test_solr_backend.py
@@ -390,6 +390,22 @@ class SolrSearchBackendTestCase(TestCase):
         self.assertEqual(self.sb.search('Index', highlight=True)['hits'], 3)
         self.assertEqual([result.highlighted['text'][0] for result in self.sb.search('Index', highlight=True)['results']], ['<em>Indexed</em>!\n1', '<em>Indexed</em>!\n2', '<em>Indexed</em>!\n3'])
 
+        # shortened highlighting options
+        highlight_dict = {'simple.pre':'<i>', 'simple.post': '</i>'}
+        self.assertEqual(self.sb.search('', highlight=highlight_dict), {'hits': 0, 'results': []})
+        self.assertEqual(self.sb.search('Index', highlight=highlight_dict)['hits'], 3)
+        self.assertEqual([result.highlighted['text'][0] for result in self.sb.search('Index', highlight=highlight_dict)['results']],
+            ['<i>Indexed</i>!\n1', '<i>Indexed</i>!\n2', '<i>Indexed</i>!\n3'])
+
+        # full-form highlighting options
+        highlight_dict = {'hl.simple.pre':'<i>', 'hl.simple.post': '</i>'}
+        self.assertEqual([result.highlighted['text'][0] for result in self.sb.search('Index', highlight=highlight_dict)['results']],
+            ['<i>Indexed</i>!\n1', '<i>Indexed</i>!\n2', '<i>Indexed</i>!\n3'])
+
+        self.assertEqual(self.sb.search('Indx')['hits'], 0)
+        self.assertEqual(self.sb.search('indax')['spelling_suggestion'], 'index')
+        self.assertEqual(self.sb.search('Indx', spelling_query='indexy')['spelling_suggestion'], 'index')
+
         self.assertEqual(self.sb.search('', facets={'name': {}}), {'hits': 0, 'results': []})
         results = self.sb.search('Index', facets={'name': {}})
         self.assertEqual(results['hits'], 3)
@@ -957,7 +973,7 @@ class LiveSolrSearchQuerySetTestCase(TestCase):
         # This will break horrifically if escaping isn't working.
         sqs = self.sqs.auto_query('"pants:rule"')
         self.assertTrue(isinstance(sqs, SearchQuerySet))
-        self.assertEqual(repr(sqs.query.query_filter), '<SQ: AND content__contains="pants:rule">')
+        self.assertEqual(repr(sqs.query.query_filter), '<SQ: AND content__content="pants:rule">')
         self.assertEqual(sqs.query.build_query(), u'("pants\\:rule")')
         self.assertEqual(len(sqs), 0)
 
@@ -1217,12 +1233,10 @@ class LiveSolrMoreLikeThisTestCase(TestCase):
 
     def test_more_like_this_defer(self):
         mi = MockModel.objects.defer('foo').get(pk=1)
-        # FIXME: this currently is known to fail because haystack.utils.loading doesn't see the
-        #        MockModel_Deferred_foo class as registered:
         deferred = self.sqs.models(MockModel).more_like_this(mi)
-        self.assertEqual(deferred.count(), 0)
-        self.assertEqual([result.pk for result in deferred], [])
-        self.assertEqual(len([result.pk for result in deferred]), 0)
+        top_results = [int(result.pk) for result in deferred[:5]]
+        for i in (14, 6, 4, 22, 10):
+            self.assertIn(i, top_results)
 
     def test_more_like_this_custom_result_class(self):
         """Ensure that swapping the ``result_class`` works"""
diff --git a/test_haystack/solr_tests/test_solr_query.py b/test_haystack/solr_tests/test_solr_query.py
index d88138a..1abe58e 100644
--- a/test_haystack/solr_tests/test_solr_query.py
+++ b/test_haystack/solr_tests/test_solr_query.py
@@ -13,7 +13,6 @@ from haystack.query import SearchQuerySet, SQ
 
 from ..core.models import AnotherMockModel, MockModel
 
-
 class SolrSearchQueryTestCase(TestCase):
     fixtures = ['base_data']
 
@@ -132,6 +131,16 @@ class SolrSearchQueryTestCase(TestCase):
         else:
             self.assertTrue(u'title:("An Infamous Article" OR "A Famous Paper")' in query)
 
+    def test_build_query_with_contains(self):
+        self.sq.add_filter(SQ(content='circular'))
+        self.sq.add_filter(SQ(title__contains='haystack'))
+        self.assertEqual(self.sq.build_query(), u'((circular) AND title:(*haystack*))')
+
+    def test_build_query_with_endswith(self):
+        self.sq.add_filter(SQ(content='circular'))
+        self.sq.add_filter(SQ(title__endswith='haystack'))
+        self.assertEqual(self.sq.build_query(), u'((circular) AND title:(*haystack))')
+
     def test_build_query_wildcard_filter_types(self):
         self.sq.add_filter(SQ(content='why'))
         self.sq.add_filter(SQ(title__startswith='haystack'))
diff --git a/test_haystack/test_managers.py b/test_haystack/test_managers.py
index a76b316..46d015e 100644
--- a/test_haystack/test_managers.py
+++ b/test_haystack/test_managers.py
@@ -168,12 +168,12 @@ class ManagerTestCase(TestCase):
     def test_auto_query(self):
         sqs = self.search_index.objects.auto_query('test search -stuff')
         self.assertTrue(isinstance(sqs, SearchQuerySet))
-        self.assertEqual(repr(sqs.query.query_filter), '<SQ: AND content__contains=test search -stuff>')
+        self.assertEqual(repr(sqs.query.query_filter), '<SQ: AND content__content=test search -stuff>')
 
         # With keyword argument
         sqs = self.search_index.objects.auto_query('test search -stuff', fieldname='title')
         self.assertTrue(isinstance(sqs, SearchQuerySet))
-        self.assertEqual(repr(sqs.query.query_filter), "<SQ: AND title__contains=test search -stuff>")
+        self.assertEqual(repr(sqs.query.query_filter), "<SQ: AND title__content=test search -stuff>")
 
     def test_autocomplete(self):
         # Not implemented
diff --git a/test_haystack/test_query.py b/test_haystack/test_query.py
index 0d2540e..761eb6c 100644
--- a/test_haystack/test_query.py
+++ b/test_haystack/test_query.py
@@ -32,8 +32,9 @@ class SQTestCase(TestCase):
     def test_split_expression(self):
         sq = SQ(foo='bar')
 
-        self.assertEqual(sq.split_expression('foo'), ('foo', 'contains'))
+        self.assertEqual(sq.split_expression('foo'), ('foo', 'content'))
         self.assertEqual(sq.split_expression('foo__exact'), ('foo', 'exact'))
+        self.assertEqual(sq.split_expression('foo__content'), ('foo', 'content'))
         self.assertEqual(sq.split_expression('foo__contains'), ('foo', 'contains'))
         self.assertEqual(sq.split_expression('foo__lt'), ('foo', 'lt'))
         self.assertEqual(sq.split_expression('foo__lte'), ('foo', 'lte'))
@@ -41,31 +42,32 @@ class SQTestCase(TestCase):
         self.assertEqual(sq.split_expression('foo__gte'), ('foo', 'gte'))
         self.assertEqual(sq.split_expression('foo__in'), ('foo', 'in'))
         self.assertEqual(sq.split_expression('foo__startswith'), ('foo', 'startswith'))
+        self.assertEqual(sq.split_expression('foo__endswith'), ('foo', 'endswith'))
         self.assertEqual(sq.split_expression('foo__range'), ('foo', 'range'))
         self.assertEqual(sq.split_expression('foo__fuzzy'), ('foo', 'fuzzy'))
 
         # Unrecognized filter. Fall back to exact.
-        self.assertEqual(sq.split_expression('foo__moof'), ('foo', 'contains'))
+        self.assertEqual(sq.split_expression('foo__moof'), ('foo', 'content'))
 
     def test_repr(self):
-        self.assertEqual(repr(SQ(foo='bar')), '<SQ: AND foo__contains=bar>')
-        self.assertEqual(repr(SQ(foo=1)), '<SQ: AND foo__contains=1>')
-        self.assertEqual(repr(SQ(foo=datetime.datetime(2009, 5, 12, 23, 17))), '<SQ: AND foo__contains=2009-05-12 23:17:00>')
+        self.assertEqual(repr(SQ(foo='bar')), '<SQ: AND foo__content=bar>')
+        self.assertEqual(repr(SQ(foo=1)), '<SQ: AND foo__content=1>')
+        self.assertEqual(repr(SQ(foo=datetime.datetime(2009, 5, 12, 23, 17))), '<SQ: AND foo__content=2009-05-12 23:17:00>')
 
     def test_simple_nesting(self):
         sq1 = SQ(foo='bar')
         sq2 = SQ(foo='bar')
         bigger_sq = SQ(sq1 & sq2)
-        self.assertEqual(repr(bigger_sq), '<SQ: AND (foo__contains=bar AND foo__contains=bar)>')
+        self.assertEqual(repr(bigger_sq), '<SQ: AND (foo__content=bar AND foo__content=bar)>')
 
         another_bigger_sq = SQ(sq1 | sq2)
-        self.assertEqual(repr(another_bigger_sq), '<SQ: AND (foo__contains=bar OR foo__contains=bar)>')
+        self.assertEqual(repr(another_bigger_sq), '<SQ: AND (foo__content=bar OR foo__content=bar)>')
 
         one_more_bigger_sq = SQ(sq1 & ~sq2)
-        self.assertEqual(repr(one_more_bigger_sq), '<SQ: AND (foo__contains=bar AND NOT (foo__contains=bar))>')
+        self.assertEqual(repr(one_more_bigger_sq), '<SQ: AND (foo__content=bar AND NOT (foo__content=bar))>')
 
         mega_sq = SQ(bigger_sq & SQ(another_bigger_sq | ~one_more_bigger_sq))
-        self.assertEqual(repr(mega_sq), '<SQ: AND ((foo__contains=bar AND foo__contains=bar) AND ((foo__contains=bar OR foo__contains=bar) OR NOT ((foo__contains=bar AND NOT (foo__contains=bar)))))>')
+        self.assertEqual(repr(mega_sq), '<SQ: AND ((foo__content=bar AND foo__content=bar) AND ((foo__content=bar OR foo__content=bar) OR NOT ((foo__content=bar AND NOT (foo__content=bar)))))>')
 
 
 class BaseSearchQueryTestCase(TestCase):
@@ -95,15 +97,15 @@ class BaseSearchQueryTestCase(TestCase):
 
         self.bsq.add_filter(SQ(claris='moof'), use_or=True)
 
-        self.assertEqual(repr(self.bsq.query_filter), '<SQ: OR ((foo__contains=bar AND foo__lt=10 AND NOT (claris__contains=moof)) OR claris__contains=moof)>')
+        self.assertEqual(repr(self.bsq.query_filter), '<SQ: OR ((foo__content=bar AND foo__lt=10 AND NOT (claris__content=moof)) OR claris__content=moof)>')
 
         self.bsq.add_filter(SQ(claris='moof'))
 
-        self.assertEqual(repr(self.bsq.query_filter), '<SQ: AND (((foo__contains=bar AND foo__lt=10 AND NOT (claris__contains=moof)) OR claris__contains=moof) AND claris__contains=moof)>')
+        self.assertEqual(repr(self.bsq.query_filter), '<SQ: AND (((foo__content=bar AND foo__lt=10 AND NOT (claris__content=moof)) OR claris__content=moof) AND claris__content=moof)>')
 
         self.bsq.add_filter(SQ(claris='wtf mate'))
 
-        self.assertEqual(repr(self.bsq.query_filter), '<SQ: AND (((foo__contains=bar AND foo__lt=10 AND NOT (claris__contains=moof)) OR claris__contains=moof) AND claris__contains=moof AND claris__contains=wtf mate)>')
+        self.assertEqual(repr(self.bsq.query_filter), '<SQ: AND (((foo__content=bar AND foo__lt=10 AND NOT (claris__content=moof)) OR claris__content=moof) AND claris__content=moof AND claris__content=wtf mate)>')
 
     def test_add_order_by(self):
         self.assertEqual(len(self.bsq.order_by), 0)
@@ -573,37 +575,37 @@ class SearchQuerySetTestCase(TestCase):
     def test_auto_query(self):
         sqs = self.msqs.auto_query('test search -stuff')
         self.assertTrue(isinstance(sqs, SearchQuerySet))
-        self.assertEqual(repr(sqs.query.query_filter), '<SQ: AND content__contains=test search -stuff>')
+        self.assertEqual(repr(sqs.query.query_filter), '<SQ: AND content__content=test search -stuff>')
 
         sqs = self.msqs.auto_query('test "my thing" search -stuff')
         self.assertTrue(isinstance(sqs, SearchQuerySet))
-        self.assertEqual(repr(sqs.query.query_filter), '<SQ: AND content__contains=test "my thing" search -stuff>')
+        self.assertEqual(repr(sqs.query.query_filter), '<SQ: AND content__content=test "my thing" search -stuff>')
 
         sqs = self.msqs.auto_query('test "my thing" search \'moar quotes\' -stuff')
         self.assertTrue(isinstance(sqs, SearchQuerySet))
-        self.assertEqual(repr(sqs.query.query_filter), '<SQ: AND content__contains=test "my thing" search \'moar quotes\' -stuff>')
+        self.assertEqual(repr(sqs.query.query_filter), '<SQ: AND content__content=test "my thing" search \'moar quotes\' -stuff>')
 
         sqs = self.msqs.auto_query('test "my thing" search \'moar quotes\' "foo -stuff')
         self.assertTrue(isinstance(sqs, SearchQuerySet))
-        self.assertEqual(repr(sqs.query.query_filter), '<SQ: AND content__contains=test "my thing" search \'moar quotes\' "foo -stuff>')
+        self.assertEqual(repr(sqs.query.query_filter), '<SQ: AND content__content=test "my thing" search \'moar quotes\' "foo -stuff>')
 
         sqs = self.msqs.auto_query('test - stuff')
         self.assertTrue(isinstance(sqs, SearchQuerySet))
-        self.assertEqual(repr(sqs.query.query_filter), "<SQ: AND content__contains=test - stuff>")
+        self.assertEqual(repr(sqs.query.query_filter), "<SQ: AND content__content=test - stuff>")
 
         # Ensure bits in exact matches get escaped properly as well.
         sqs = self.msqs.auto_query('"pants:rule"')
         self.assertTrue(isinstance(sqs, SearchQuerySet))
-        self.assertEqual(repr(sqs.query.query_filter), '<SQ: AND content__contains="pants:rule">')
+        self.assertEqual(repr(sqs.query.query_filter), '<SQ: AND content__content="pants:rule">')
 
         # Now with a different fieldname
         sqs = self.msqs.auto_query('test search -stuff', fieldname='title')
         self.assertTrue(isinstance(sqs, SearchQuerySet))
-        self.assertEqual(repr(sqs.query.query_filter), "<SQ: AND title__contains=test search -stuff>")
+        self.assertEqual(repr(sqs.query.query_filter), "<SQ: AND title__content=test search -stuff>")
 
         sqs = self.msqs.auto_query('test "my thing" search -stuff', fieldname='title')
         self.assertTrue(isinstance(sqs, SearchQuerySet))
-        self.assertEqual(repr(sqs.query.query_filter), '<SQ: AND title__contains=test "my thing" search -stuff>')
+        self.assertEqual(repr(sqs.query.query_filter), '<SQ: AND title__content=test "my thing" search -stuff>')
 
     def test_count(self):
         self.assertEqual(self.msqs.count(), 23)
diff --git a/test_haystack/test_views.py b/test_haystack/test_views.py
index 157cb47..bbd4030 100644
--- a/test_haystack/test_views.py
+++ b/test_haystack/test_views.py
@@ -8,6 +8,7 @@ from django import forms
 from django.conf import settings
 from django.core.urlresolvers import reverse
 from django.http import HttpRequest, QueryDict
+from django.test import override_settings
 from django.test import TestCase
 from django.utils.six.moves import queue
 from test_haystack.core.models import AnotherMockModel, MockModel
@@ -152,9 +153,9 @@ class SearchViewTestCase(TestCase):
             del settings.HAYSTACK_CONNECTIONS['default']['INCLUDE_SPELLING']
 
 
+ at override_settings(ROOT_URLCONF='test_haystack.results_per_page_urls')
 class ResultsPerPageTestCase(TestCase):
     fixtures = ['base_data']
-    urls = 'test_haystack.results_per_page_urls'
 
     def setUp(self):
         super(ResultsPerPageTestCase, self).setUp()
diff --git a/test_haystack/whoosh_tests/test_whoosh_backend.py b/test_haystack/whoosh_tests/test_whoosh_backend.py
index 3bd31ac..4e74fee 100644
--- a/test_haystack/whoosh_tests/test_whoosh_backend.py
+++ b/test_haystack/whoosh_tests/test_whoosh_backend.py
@@ -901,12 +901,11 @@ class LiveWhooshMoreLikeThisTestCase(WhooshTestCase):
 
         if hasattr(MockModel.objects, 'defer'):
             # Make sure MLT works with deferred bits.
-            mi = MockModel.objects.defer('foo').get(pk=21)
-            self.assertEqual(mi._deferred, True)
+            mi = MockModel.objects.defer('foo').get(pk=22)
             deferred = self.sqs.models(MockModel).more_like_this(mi)
-            self.assertEqual(deferred.count(), 0)
-            self.assertEqual([result.pk for result in deferred], [])
-            self.assertEqual(len([result.pk for result in deferred]), 0)
+            self.assertEqual(deferred.count(), 22)
+            self.assertEqual(sorted([result.pk for result in deferred]), sorted([u'9', u'8', u'7', u'6', u'5', u'4', u'3', u'2', u'1', u'21', u'20', u'19', u'18', u'17', u'16', u'15', u'14', u'13', u'12', u'11', u'10', u'23']))
+            self.assertEqual(len([result.pk for result in deferred]), 22)
 
         # Ensure that swapping the ``result_class`` works.
         self.assertTrue(isinstance(self.sqs.result_class(MockSearchResult).more_like_this(MockModel.objects.get(pk=21))[0], MockSearchResult))
diff --git a/test_haystack/whoosh_tests/test_whoosh_query.py b/test_haystack/whoosh_tests/test_whoosh_query.py
index f0e0205..16d56b2 100644
--- a/test_haystack/whoosh_tests/test_whoosh_query.py
+++ b/test_haystack/whoosh_tests/test_whoosh_query.py
@@ -103,6 +103,16 @@ class WhooshSearchQueryTestCase(WhooshTestCase):
         self.sq.add_filter(SQ(title__fuzzy='haystack'))
         self.assertEqual(self.sq.build_query(), u'((why) AND title:(haystack~))')
 
+    def test_build_query_with_contains(self):
+        self.sq.add_filter(SQ(content='circular'))
+        self.sq.add_filter(SQ(title__contains='haystack'))
+        self.assertEqual(self.sq.build_query(), u'((circular) AND title:(*haystack*))')
+
+    def test_build_query_with_endswith(self):
+        self.sq.add_filter(SQ(content='circular'))
+        self.sq.add_filter(SQ(title__endswith='haystack'))
+        self.assertEqual(self.sq.build_query(), u'((circular) AND title:(*haystack))')
+
     def test_clean(self):
         self.assertEqual(self.sq.clean('hello world'), 'hello world')
         self.assertEqual(self.sq.clean('hello AND world'), 'hello and world')
diff --git a/tox.ini b/tox.ini
index b63dc9c..b44f2ee 100644
--- a/tox.ini
+++ b/tox.ini
@@ -2,16 +2,24 @@
 envlist = docs,
         py27-django1.8,
         py27-django1.9,
+        py27-django1.10,
         py34-django1.8,
         py34-django1.9,
+        py34-django1.10,
         py35-django1.8,
         py35-django1.9,
+        py35-django1.10,
         pypy-django1.8,
         pypy-django1.9,
+        pypy-django1.10,
 
 [base]
 deps = requests
 
+[django1.10]
+deps =
+    Django>=1.10,<1.11
+
 [django1.9]
 deps =
     Django>=1.9,<1.10
@@ -35,6 +43,11 @@ deps =
     {[django1.9]deps}
     {[base]deps}
 
+[testenv:pypy-django1.10]
+deps =
+    {[django1.10]deps}
+    {[base]deps}
+
 [testenv:py27-django1.8]
 basepython = python2.7
 deps =
@@ -47,6 +60,12 @@ deps =
     {[django1.9]deps}
     {[base]deps}
 
+[testenv:py27-django1.10]
+basepython = python2.7
+deps =
+    {[django1.10]deps}
+    {[base]deps}
+
 [testenv:py34-django1.8]
 basepython = python3.4
 deps =
@@ -59,6 +78,12 @@ deps =
     {[django1.9]deps}
     {[base]deps}
 
+[testenv:py34-django1.10]
+basepython = python3.4
+deps =
+    {[django1.10]deps}
+    {[base]deps}
+
 [testenv:py35-django1.8]
 basepython = python3.5
 deps =
@@ -71,6 +96,12 @@ deps =
     {[django1.9]deps}
     {[base]deps}
 
+[testenv:py35-django1.10]
+basepython = python3.5
+deps =
+    {[django1.10]deps}
+    {[base]deps}
+
 [testenv:docs]
 changedir = docs
 deps =

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/django-haystack.git



More information about the Python-modules-commits mailing list