[Python-modules-commits] [drf-haystack] 01/10: Import drf-haystack_1.6.1.orig.tar.gz
Michael Fladischer
fladi at moszumanska.debian.org
Tue Mar 21 12:13:41 UTC 2017
This is an automated email from the git hooks/post-receive script.
fladi pushed a commit to branch experimental
in repository drf-haystack.
commit 2f1644596809464c3afe3363e8ce24f58b31f26d
Author: Michael Fladischer <FladischerMichael at fladi.at>
Date: Tue Mar 21 12:48:23 2017 +0100
Import drf-haystack_1.6.1.orig.tar.gz
---
MANIFEST.in | 2 +-
PKG-INFO | 6 +-
README.md | 66 ++
README.rst | 52 --
docs/{basic_usage.rst => 01_intro.rst} | 49 +-
docs/02_autocomplete.rst | 48 ++
docs/03_geospatial.rst | 86 ++
docs/04_highlighting.rst | 132 +++
docs/05_more_like_this.rst | 64 ++
docs/06_term_boost.rst | 55 ++
docs/07_faceting.rst | 316 +++++++
docs/08_permissions.rst | 34 +
docs/09_multiple_indexes.rst | 168 ++++
docs/10_tips_n_tricks.rst | 101 +++
docs/advanced_usage.rst | 936 ---------------------
docs/apidoc/drf_haystack.rst | 64 ++
docs/apidoc/modules.rst | 7 +
docs/conf.py | 23 +-
docs/index.rst | 109 ++-
drf_haystack.egg-info/PKG-INFO | 6 +-
drf_haystack.egg-info/SOURCES.txt | 22 +-
drf_haystack.egg-info/pbr.json | 1 +
drf_haystack.egg-info/requires.txt | 6 +-
drf_haystack/__init__.py | 2 +-
drf_haystack/constants.py | 4 +
drf_haystack/fields.py | 51 +-
drf_haystack/filters.py | 377 +++------
drf_haystack/generics.py | 112 +--
drf_haystack/mixins.py | 122 +++
drf_haystack/query.py | 311 +++++++
drf_haystack/serializers.py | 330 +++++---
drf_haystack/viewsets.py | 52 +-
requirements.txt | 6 +-
setup.cfg | 2 +-
setup.py | 10 +-
tests/__init__.py | 11 +-
tests/mockapp/apps.py | 9 +
tests/mockapp/fixtures/mockperson.json | 702 +++++++++-------
tests/mockapp/migrations/0004_load_fixtures.py | 1 +
.../migrations/0005_mockperson_birthdate.py | 21 +
tests/mockapp/models.py | 12 +
tests/mockapp/search_indexes.py | 11 +
tests/mockapp/serializers.py | 10 +-
tests/mockapp/views.py | 28 +-
tests/runtests.py | 7 +-
tests/settings.py | 49 +-
tests/test_filters.py | 74 +-
tests/test_serializers.py | 391 ++++-----
tests/test_viewsets.py | 128 ++-
tests/urls.py | 14 +-
tox.ini | 132 +--
51 files changed, 3006 insertions(+), 2326 deletions(-)
diff --git a/MANIFEST.in b/MANIFEST.in
index 4fb1247..b8951d5 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,4 +1,4 @@
-include README.rst
+include README.md
include LICENSE.txt
include requirements.txt
include tox.ini
diff --git a/PKG-INFO b/PKG-INFO
index 4a054d3..857c785 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,10 +1,10 @@
Metadata-Version: 1.1
Name: drf-haystack
-Version: 1.5.6
+Version: 1.6.1
Summary: Makes Haystack play nice with Django REST Framework
Home-page: https://github.com/inonit/drf-haystack
-Author: Rolf Håvard Blindheim, Eirik Krogstad
-Author-email: rolf.blindheim at inonit.no, eirik.krogstad at inonit.no
+Author: Rolf Håvard Blindheim
+Author-email: rolf.blindheim at inonit.no
License: MIT License
Download-URL: https://github.com/inonit/drf-haystack.git
Description: Implements a ViewSet, FiltersBackends and Serializers in order to play nice with Haystack.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0a5b798
--- /dev/null
+++ b/README.md
@@ -0,0 +1,66 @@
+Haystack for Django REST Framework
+==================================
+
+Build status
+------------
+
+[](https://travis-ci.org/inonit/drf-haystack)
+[](https://coveralls.io/github/inonit/drf-haystack?branch=master)
+[](https://badge.fury.io/py/drf-haystack)
+[](http://drf-haystack.readthedocs.io/en/latest/?badge=latest)
+
+
+About
+-----
+
+Small library which tries to simplify integration of Haystack with Django REST Framework.
+Fresh [documentation available](https://drf-haystack.readthedocs.io/en/latest/>) on Read the docs!
+
+Supported versions
+------------------
+
+- Python 2.7+ and Python 3.4+
+- [All supported versions of Django](https://www.djangoproject.com/download/#supported-versions>)
+- Haystack 2.5 and above
+- Django REST Framework 3.2 and above
+
+
+Installation
+------------
+
+ $ pip install drf-haystack
+
+Supported features
+------------------
+We aim to support most features Haystack does (or at least those which can be used in a REST API).
+Currently we support:
+
+- Autocomplete
+- Boost (Experimental)
+- Faceting
+- Geo Spatial Search
+- Highlighting
+- More Like This
+
+Show me more!
+-------------
+
+```
+from drf_haystack.serializers import HaystackSerializer
+from drf_haystack.viewsets import HaystackViewSet
+
+from myapp.search_indexes import PersonIndex # BYOI™ (Bring Your Own Index)
+
+# Serializer
+class PersonSerializer(HaystackSerializer):
+ class Meta:
+ index_classes = [PersonIndex]
+ fields = ["firstname", "lastname", "full_name"]
+
+# ViewSet
+class PersonSearchViewSet(HaystackViewSet):
+ index_models = [Person]
+ serializer_class = PersonSerializer
+```
+
+That's it, you're good to go. Hook it up to a DRF router and happy searching!
diff --git a/README.rst b/README.rst
deleted file mode 100644
index eb4b3f7..0000000
--- a/README.rst
+++ /dev/null
@@ -1,52 +0,0 @@
-Haystack for Django REST Framework
-==================================
-
-Build status
-------------
-
-.. image:: https://travis-ci.org/inonit/drf-haystack.svg?branch=master
- :target: https://travis-ci.org/inonit/drf-haystack
-
-.. image:: https://readthedocs.org/projects/drf-haystack/badge/?version=latest
- :target: https://readthedocs.org/projects/drf-haystack/?badge=latest
- :alt: Documentation Status
-
-.. image:: https://pypip.in/d/drf-haystack/badge.png
- :target: https://pypi.python.org/pypi/drf-haystack
-
-About
------
-
-Small library which tries to simplify integration of Haystack with Django REST Framework.
-Contains a Generic ViewSet, a Serializer and a couple of Filters in order to make search as
-painless as possible.
-
-Fresh `documentation available <http://drf-haystack.readthedocs.org/en/latest/>`_ on Read the docs!
-
-
-
-Supported Python and Django versions
-------------------------------------
-
-Tested with the following configurations:
-
- - Python 2.6
- - Django 1.5 and 1.6
- - Python 2.7, 3.3 and 3.4
- - Django 1.5, 1.6, 1.7 and 1.8
-
-Installation
-------------
-
- $ pip install drf-haystack
-
-Supported features
-------------------
-We aim to support most features Haystack does (or at least those which can be used in a REST API).
-Currently we support:
-
- * Autocomplete
- * GEO Spatial searching
- * Highlighting
- * More Like This
- * Faceting
diff --git a/docs/basic_usage.rst b/docs/01_intro.rst
similarity index 81%
rename from docs/basic_usage.rst
rename to docs/01_intro.rst
index 66a5185..719ee96 100644
--- a/docs/basic_usage.rst
+++ b/docs/01_intro.rst
@@ -53,7 +53,7 @@ Let's say we have an app which contains a model `Location`. It could look someth
search_indexes.py
-----------------
-We would have to make a `search_indexes.py` file for haystack to pick it up.
+We would have to make a ``search_indexes.py`` file for haystack to pick it up.
.. code-block:: python
@@ -131,7 +131,7 @@ For a generic Django REST Framework view, you could do something like this.
# a way to filter out those of no interest for this particular view.
# (Translates to `SearchQuerySet().models(*index_models)` behind the scenes.
index_models = [Location]
-
+
serializer_class = LocationSerializer
@@ -183,39 +183,30 @@ basic search by querying any of the field included in the `fields` attribute on
Would perform a query looking up all documents where the `city field` equals "Oslo".
-Regular Search View
-===================
-
-Sometimes you might not need all the bells and whistles of a `ViewSet`,
-but can do with a regular view. In such scenario you could do something like this.
-
-.. code-block:: python
-
- #
- # views.py
- #
-
- from rest_framework.mixins import ListModelMixin
- from drf_haystack.generics import HaystackGenericAPIView
+Field Lookups
+.............
+You can also use field lookups in your field queries. See the
+Haystack `field lookups <https://django-haystack.readthedocs.io/en/latest/searchqueryset_api.html?highlight=lookups#id1>`_
+documentation for info on what lookups are available. A query using a lookup might look like the
+following:
- class SearchView(ListModelMixin, HaystackGenericAPIView):
+.. code-block:: none
- serializer_class = LocationSerializer
+ http://example.com/api/v1/location/search/?city__startswith=Os
- def get(self, request, *args, **kwargs):
- return self.list(request, *args, **kwargs)
+This would perform a query looking up all documents where the `city field` started with "Os".
+You might get "Oslo", "Osaka", and "Ostrava".
+Term Negation
+.............
- #
- # urls.py
- #
+You can also specify terms to exclude from the search results using the negation keyword.
+The default keyword is ``not``, but is configurable via settings using ``DRF_HAYSTACK_NEGATION_KEYWORD``.
- urlpatterns = (
- ...
- url(r'^search/', SearchView.as_view()),
- ...
- )
+.. code-block:: none
+ http://example.com/api/v1/location/search/?city__not=Oslo
+ http://example.com/api/v1/location/search/?city__not__contains=Los
+ http://example.com/api/v1/location/search/?city__contains=Los&city__not__contains=Angeles
-Next, check out the :ref:`advanced-usage-label`.
diff --git a/docs/02_autocomplete.rst b/docs/02_autocomplete.rst
new file mode 100644
index 0000000..98ac0a1
--- /dev/null
+++ b/docs/02_autocomplete.rst
@@ -0,0 +1,48 @@
+.. _autocomplete-label:
+
+Autocomplete
+============
+
+Some kind of data such as ie. cities and zip codes could be useful to autocomplete.
+We have a Django REST Framework filter for performing autocomplete queries. It works
+quite like the regular :class:`drf_haystack.filters.HaystackFilter` but *must* be run
+against an ``NgramField`` or ``EdgeNgramField`` in order to work properly. The main
+difference is that while the HaystackFilter performs a bitwise ``OR`` on terms for the
+same parameters, the :class:`drf_haystack.filters.HaystackAutocompleteFilter` reduce query
+parameters down to a single filter (using an ``SQ`` object), and performs a bitwise ``AND``.
+
+By adding a list or tuple of ``ignore_fields`` to the serializer's Meta class,
+we can tell the REST framework to ignore these fields. This is handy in cases,
+where you do not want to serialize and transfer the content of a text, or n-gram
+index down to the client.
+
+An example using the autocomplete filter might look something like this.
+
+
+.. code-block:: python
+
+ from drf_haystack.filters import HaystackAutocompleteFilter
+ from drf_haystack.serializers import HaystackSerializer
+ from drf_haystack.viewsets import HaystackViewSet
+
+ class AutocompleteSerializer(HaystackSerializer):
+
+ class Meta:
+ index_classes = [LocationIndex]
+ fields = ["address", "city", "zip_code", "autocomplete"]
+ ignore_fields = ["autocomplete"]
+
+ # The `field_aliases` attribute can be used in order to alias a
+ # query parameter to a field attribute. In this case a query like
+ # /search/?q=oslo would alias the `q` parameter to the `autocomplete`
+ # field on the index.
+ field_aliases = {
+ "q": "autocomplete"
+ }
+
+ class AutocompleteSearchViewSet(HaystackViewSet):
+
+ index_models = [Location]
+ serializer_class = AutocompleteSerializer
+ filter_backends = [HaystackAutocompleteFilter]
+
diff --git a/docs/03_geospatial.rst b/docs/03_geospatial.rst
new file mode 100644
index 0000000..1f9995e
--- /dev/null
+++ b/docs/03_geospatial.rst
@@ -0,0 +1,86 @@
+.. _geospatial-label:
+
+GEO spatial locations
+=====================
+
+Some search backends support geo spatial searching. In order to take advantage of this we
+have the :class:`drf_haystack.filters.HaystackGEOSpatialFilter`.
+
+.. note::
+
+ The ``HaystackGEOSpatialFilter`` depends on ``geopy`` and ``libgeos``. Make sure to install these
+ libraries in order to use this filter.
+
+ .. code-block:: none
+
+ $ pip install geopy
+ $ apt-get install libgeos-c1 (for debian based linux distros)
+ or
+ $ brew install geos (for homebrew on OS X)
+
+
+The geospatial filter is somewhat special, and for the time being, relies on a few assumptions.
+
+#. The index model **must** to have a ``LocationField`` (See :ref:`search-index-example-label` for example).
+ If your ``LocationField`` is named something other than ``coordinates``, subclass the ``HaystackGEOSpatialFilter``
+ and make sure to set the :attr:`drf_haystack.filters.HaystackGEOSpatialFilter.point_field` to the name of the field.
+#. The query **must** contain a ``unit`` parameter where the unit is a valid ``UNIT`` in the ``django.contrib.gis.measure.Distance`` class.
+#. The query **must** contain a ``from`` parameter which is a comma separated longitude and latitude value.
+
+
+**Example Geospatial view**
+
+.. code-block:: python
+
+ class DistanceSerializer(serializers.Serializer):
+ m = serializers.FloatField()
+ km = serializers.FloatField()
+
+
+ class LocationSerializer(HaystackSerializer):
+
+ distance = SerializerMethodField()
+
+ class Meta:
+ index_classes = [LocationIndex]
+ fields = ["address", "city", "zip_code"]
+
+ def get_distance(self, obj):
+ if hasattr(obj, "distance"):
+ return DistanceSerializer(obj.distance, many=False).data
+
+
+ class LocationGeoSearchViewSet(HaystackViewSet):
+
+ index_models = [Location]
+ serializer_class = LocationSerializer
+ filter_backends = [HaystackGEOSpatialFilter]
+
+
+**Example subclassing the HaystackGEOSpatialFilter**
+
+Assuming that your ``LocationField`` is named ``location``.
+
+.. code-block:: python
+
+ from drf_haystack.filters import HaystackGEOSpatialFilter
+
+ class CustomHaystackGEOSpatialFilter(HaystackGEOSpatialFilter):
+ point_field = 'location'
+
+
+ class LocationGeoSearchViewSet(HaystackViewSet):
+
+ index_models = [Location]
+ serializer_class = LocationSerializer
+ filter_backends = [CustomHaystackGEOSpatialFilter]
+
+Assuming the above code works as it should, we would be able to do queries like this:
+
+.. code-block:: none
+
+ /api/v1/search/?zip_code=0351&km=10&from=59.744076,10.152045
+
+
+The above query would return all entries with zip_code 0351 within 10 kilometers
+from the location with latitude 59.744076 and longitude 10.152045.
diff --git a/docs/04_highlighting.rst b/docs/04_highlighting.rst
new file mode 100644
index 0000000..890538e
--- /dev/null
+++ b/docs/04_highlighting.rst
@@ -0,0 +1,132 @@
+.. _highlighting-label:
+
+Highlighting
+============
+
+Haystack supports two kinds of `Highlighting <https://django-haystack.readthedocs.io/en/latest/highlighting.html>`_,
+and we support them both.
+
+#. SearchQuerySet highlighting. This kind of highlighting requires a search backend which has support for
+ highlighting, such as Elasticsearch or Solr.
+#. Pure python highlighting. This implementation is somewhat slower, but enables highlighting support
+ even if your search backend does not support it.
+
+
+.. note::
+
+ The highlighter will always use the ``document=True`` field on your index to hightlight on.
+ See examples below.
+
+SearchQuerySet Highlighting
+---------------------------
+
+In order to add support for ``SearchQuerySet().highlight()``, all you have to do is to add the
+:class:`drf_haystack.filters.HaystackHighlightFilter` to the ``filter_backends`` in your view. The ``HaystackSerializer`` will
+check if your queryset has highlighting enabled, and render an additional ``highlighted`` field to
+your result. The highlighted words will be encapsulated in an ``<em>words go here</em>`` html tag.
+
+
+**Example view with highlighting enabled**
+
+.. code-block:: python
+
+ from drf_haystack.viewsets import HaystackViewSet
+ from drf_haystack.filters import HaystackHighlightFilter
+
+ from .models import Person
+ from .serializers import PersonSerializer
+
+
+ class SearchViewSet(HaystackViewSet):
+ index_models = [Person]
+ serializer_class = PersonSerializer
+ filter_backends = [HaystackHighlightFilter]
+
+
+Given a query like below
+
+.. code-block:: none
+
+ /api/v1/search/?firstname=jeremy
+
+
+We would get a result like this
+
+.. code-block:: json
+
+ [
+ {
+ "lastname": "Rowland",
+ "full_name": "Jeremy Rowland",
+ "firstname": "Jeremy",
+ "highlighted": "<em>Jeremy</em> Rowland\nCreated: May 19, 2015, 10:48 a.m.\nLast modified: May 19, 2015, 10:48 a.m.\n"
+ },
+ {
+ "lastname": "Fowler",
+ "full_name": "Jeremy Fowler",
+ "firstname": "Jeremy",
+ "highlighted": "<em>Jeremy</em> Fowler\nCreated: May 19, 2015, 10:48 a.m.\nLast modified: May 19, 2015, 10:48 a.m.\n"
+ }
+ ]
+
+
+
+Pure Python Highlighting
+------------------------
+
+This implementation make use of the haystack ``Highlighter()`` class.
+It is implemented as :class:`drf_haystack.serializers.HighlighterMixin` mixin class, and must be applied on the ``Serializer``.
+This is somewhat slower, but more configurable than the :class:`drf_haystack.filters.HaystackHighlightFilter` filter class.
+
+The Highlighter class will be initialized with the following default options, but can be overridden by
+changing any of the following class attributes.
+
+ .. code-block:: python
+
+ highlighter_class = Highlighter
+ highlighter_css_class = "highlighted"
+ highlighter_html_tag = "span"
+ highlighter_max_length = 200
+ highlighter_field = None
+
+The Highlighter class will usually highlight the ``document_field`` (the field marked ``document=True`` on your
+search index class), but this may be overridden by changing the ``highlighter_field``.
+
+You can of course also use your own ``Highlighter`` class by overriding the ``highlighter_class = MyFancyHighLighter``
+class attribute.
+
+
+**Example serializer with highlighter support**
+
+.. code-block:: python
+
+ from drf_haystack.serializers import HighlighterMixin, HaystackSerializer
+
+ class PersonSerializer(HighlighterMixin, HaystackSerializer):
+
+ highlighter_css_class = "my-highlighter-class"
+ highlighter_html_tag = "em"
+
+ class Meta:
+ index_classes = [PersonIndex]
+ fields = ["firstname", "lastname", "full_name"]
+
+
+Response
+
+.. code-block:: json
+
+ [
+ {
+ "full_name": "Jeremy Rowland",
+ "lastname": "Rowland",
+ "firstname": "Jeremy",
+ "highlighted": "<em class=\"my-highlighter-class\">Jeremy</em> Rowland\nCreated: May 19, 2015, 10:48 a.m.\nLast modified: May 19, 2015, 10:48 a.m.\n"
+ },
+ {
+ "full_name": "Jeremy Fowler",
+ "lastname": "Fowler",
+ "firstname": "Jeremy",
+ "highlighted": "<em class=\"my-highlighter-class\">Jeremy</em> Fowler\nCreated: May 19, 2015, 10:48 a.m.\nLast modified: May 19, 2015, 10:48 a.m.\n"
+ }
+ ]
diff --git a/docs/05_more_like_this.rst b/docs/05_more_like_this.rst
new file mode 100644
index 0000000..2d9d34f
--- /dev/null
+++ b/docs/05_more_like_this.rst
@@ -0,0 +1,64 @@
+.. _more-like-this-label:
+
+More Like This
+==============
+
+Some search backends supports ``More Like This`` features. In order to take advantage of this,
+we have a mixin class :class:`drf_haystack.mixins.MoreLikeThisMixin`, which will append a ``more-like-this``
+detail route to the base name of the ViewSet. Lets say you have a router which looks like this:
+
+.. code-block:: python
+
+ router = routers.DefaultRouter()
+ router.register("search", viewset=SearchViewSet, base_name="search") # MLT name will be 'search-more-like-this'.
+
+ urlpatterns = patterns(
+ "",
+ url(r"^", include(router.urls))
+ )
+
+The important thing here is that the ``SearchViewSet`` class inherits from the
+:class:`drf_haystack.mixins.MoreLikeThisMixin` class in order to get the ``more-like-this`` route automatically added.
+The view name will be ``{base_name}-more-like-this``, which in this case would be for example ``search-more-like-this``.
+
+
+Serializing the More Like This URL
+----------------------------------
+
+In order to include the ``more-like-this`` url in your result you only have to add a ``HyperlinkedIdentityField``
+to your serializer.
+Something like this should work okay.
+
+**Example serializer with More Like This**
+
+.. code-block:: python
+
+ class SearchSerializer(HaystackSerializer):
+
+ more_like_this = serializers.HyperlinkedIdentityField(view_name="search-more-like-this", read_only=True)
+
+ class Meta:
+ index_classes = [PersonIndex]
+ fields = ["firstname", "lastname", "full_name"]
+
+
+ class SearchViewSet(MoreLikeThisMixin, HaystackViewSet):
+ index_models = [Person]
+ serializer_class = SearchSerializer
+
+
+Now, every result you render with this serializer will include a ``more_like_this`` field containing the url
+for similar results.
+
+Example response
+
+.. code-block:: json
+
+ [
+ {
+ "full_name": "Jeremy Rowland",
+ "lastname": "Rowland",
+ "firstname": "Jeremy",
+ "more_like_this": "http://example.com/search/5/more-like-this/"
+ }
+ ]
diff --git a/docs/06_term_boost.rst b/docs/06_term_boost.rst
new file mode 100644
index 0000000..cc4302e
--- /dev/null
+++ b/docs/06_term_boost.rst
@@ -0,0 +1,55 @@
+.. _term-boost-label:
+
+Term Boost
+==========
+
+.. warning::
+
+ **BIG FAT WARNING**
+
+ As far as I can see, the term boost functionality is implemented by the specs in the
+ `Haystack documentation <https://django-haystack.readthedocs.io/en/v2.4.0/boost.html#term-boost>`_,
+ however it does not really work as it should!
+
+ When applying term boost, results are discarded from the search result, and not re-ordered by
+ boost weight as they should.
+ These are known problems and there exists open issues for them:
+
+ - https://github.com/inonit/drf-haystack/issues/21
+ - https://github.com/django-haystack/django-haystack/issues/1235
+ - https://github.com/django-haystack/django-haystack/issues/508
+
+ **Please do not use this unless you really know what you are doing!**
+
+ (And please let me know if you know how to fix it!)
+
+
+Term boost is achieved on the SearchQuerySet level by calling ``SearchQuerySet().boost()``. It is
+implemented as a :class:`drf_haystack.filters.HaystackBoostFilter` filter backend.
+The ``HaystackBoostFilter`` does not perform any filtering by itself, and should therefore be combined with
+some other filter that does, for example the :class:`drf_haystack.filters.HaystackFilter`.
+
+.. code-block:: python
+
+ from drf_haystack.filters import HaystackBoostFilter
+
+ class SearchViewSet(HaystackViewSet):
+ ...
+ filter_backends = [HaystackFilter, HaystackBoostFilter]
+
+
+The filter expects the query string to contain a ``boost`` parameter, which is a comma separated string
+of the term to boost and the boost value. The boost value must be either an integer or float value.
+
+**Example query**
+
+.. code-block:: none
+
+ /api/v1/search/?firstname=robin&boost=hood,1.1
+
+The query above will first filter on ``firstname=robin`` and next apply a slight boost on any document containing
+the word ``hood``.
+
+.. note::
+
+ Term boost are only applied on terms existing in the ``document field``.
diff --git a/docs/07_faceting.rst b/docs/07_faceting.rst
new file mode 100644
index 0000000..1425fe6
--- /dev/null
+++ b/docs/07_faceting.rst
@@ -0,0 +1,316 @@
+.. _faceting-label:
+
+Faceting
+========
+
+Faceting is a way of grouping and narrowing search results by a common factor, for example we can group
+all results which are registered on a certain date. Similar to :ref:`more-like-this-label`, the faceting
+functionality is implemented by setting up a special ``^search/facets/$`` route on any view which inherits from the
+:class:`drf_haystack.mixins.FacetMixin` class.
+
+
+.. note::
+
+ Options used for faceting is **not** portable across search backends. Make sure to provide
+ options suitable for the backend you're using.
+
+
+First, read the `Haystack faceting docs <https://django-haystack.readthedocs.io/en/latest/faceting.html>`_ and set up
+your search index for faceting.
+
+Serializing faceted counts
+--------------------------
+
+Faceting is a little special in terms that it *does not* care about SearchQuerySet filtering. Faceting is performed
+by calling the ``SearchQuerySet().facet(field, **options)`` and ``SearchQuerySet().date_facet(field, **options)``
+methods, which will apply facets to the SearchQuerySet. Next we need to call the ``SearchQuerySet().facet_counts()``
+in order to retrieve a dictionary with all the *counts* for the faceted fields.
+We have a special :class:`drf_haystack.serializers.HaystackFacetSerializer` class which is designed to serialize
+these results.
+
+.. tip::
+
+ It *is* possible to perform faceting on a subset of the queryset, in which case you'd have to override the
+ ``get_queryset()`` method of the view to limit the queryset before it is passed on to the
+ ``filter_facet_queryset()`` method.
+
+Any serializer subclassed from the ``HaystackFacetSerializer`` is expected to have a ``field_options`` dictionary
+containing a set of default options passed to ``facet()`` and ``date_facet()``.
+
+**Facet serializer example**
+
+.. code-block:: python
+
+ class PersonFacetSerializer(HaystackFacetSerializer):
+
+ serialize_objects = False # Setting this to True will serialize the
+ # queryset into an `objects` list. This
+ # is useful if you need to display the faceted
+ # results. Defaults to False.
+ class Meta:
+ index_classes = [PersonIndex]
+ fields = ["firstname", "lastname", "created"]
+ field_options = {
+ "firstname": {},
+ "lastname": {},
+ "created": {
+ "start_date": datetime.now() - timedelta(days=3 * 365),
+ "end_date": datetime.now(),
+ "gap_by": "month",
+ "gap_amount": 3
+ }
+ }
+
+The declared ``field_options`` will be used as default options when faceting is applied to the queryset, but can be
+overridden by supplying query string parameters in the following format.
+
+ .. code-block:: none
+
+ ?firstname=limit:1&created=start_date:20th May 2014,gap_by:year
+
+Each field can be fed options as ``key:value`` pairs. Multiple ``key:value`` pairs can be supplied and
+will be separated by the ``view.lookup_sep`` attribute (which defaults to comma). Any ``start_date`` and ``end_date``
+parameters will be parsed by the python-dateutil
+`parser() <https://labix.org/python-dateutil#head-a23e8ae0a661d77b89dfb3476f85b26f0b30349c>`_ (which can handle most
+common date formats).
+
+ .. note::
+
+ - The ``HaystackFacetFilter`` parses query string parameter options, separated with the ``view.lookup_sep``
+ attribute. Each option is parsed as ``key:value`` pairs where the ``:`` is a hardcoded separator. Setting
+ the ``view.lookup_sep`` attribute to ``":"`` will raise an AttributeError.
+
+ - The date parsing in the ``HaystackFacetFilter`` does intentionally blow up if fed a string format it can't
+ handle. No exception handling is done, so make sure to convert values to a format you know it can handle
+ before passing it to the filter. Ie., don't let your users feed their own values in here ;)
+
+ .. warning::
+
+ Do *not* use the ``HaystackFacetFilter`` in the regular ``filter_backends`` list on the serializer.
+ It will almost certainly produce errors or weird results. Faceting filters should go in the
+ ``facet_filter_backends`` list.
+
+**Example serialized content**
+
+The serialized content will look a little different than the default Haystack faceted output.
+The top level items will *always* be **queries**, **fields** and **dates**, each containing a subset of fields
+matching the category. In the example below, we have faceted on the fields *firstname* and *lastname*, which will
+make them appear under the **fields** category. We also have faceted on the date field *created*, which will show up
+under the **dates** category. Next, each faceted result will have a ``text``, ``count`` and ``narrow_url``
+attribute which should be quite self explaining.
+
+ .. code-block:: json
+
+ {
+ "queries": {},
+ "fields": {
+ "firstname": [
+ {
+ "text": "John",
+ "count": 3,
+ "narrow_url": "http://example.com/api/v1/search/facets/?selected_facets=firstname_exact%3AJohn"
+ },
+ {
+ "text": "Randall",
+ "count": 2,
+ "narrow_url": "http://example.com/api/v1/search/facets/?selected_facets=firstname_exact%3ARandall"
+ },
+ {
+ "text": "Nehru",
+ "count": 2,
+ "narrow_url": "http://example.com/api/v1/search/facets/?selected_facets=firstname_exact%3ANehru"
+ }
+ ],
+ "lastname": [
+ {
+ "text": "Porter",
+ "count": 2,
+ "narrow_url": "http://example.com/api/v1/search/facets/?selected_facets=lastname_exact%3APorter"
+ },
+ {
+ "text": "Odonnell",
+ "count": 2,
+ "narrow_url": "http://example.com/api/v1/search/facets/?selected_facets=lastname_exact%3AOdonnell"
+ },
+ {
+ "text": "Hood",
+ "count": 2,
+ "narrow_url": "http://example.com/api/v1/search/facets/?selected_facets=lastname_exact%3AHood"
+ }
+ ]
+ },
+ "dates": {
+ "created": [
+ {
+ "text": "2015-05-15T00:00:00",
+ "count": 100,
+ "narrow_url": "http://example.com/api/v1/search/facets/?selected_facets=created_exact%3A2015-05-15+00%3A00%3A00"
+ }
+ ]
+ }
+ }
+
+
+Serializing faceted results
+---------------------------
+
+When a ``HaystackFacetSerializer`` class determines what fields to serialize, it will check
+the ``serialize_objects`` class attribute to see if it is ``True`` or ``False``. Setting this value to ``True``
+will add an additional ``objects`` field to the serialized results, which will contain the results for the
+faceted ``SearchQuerySet``. The results will by default be serialized using the view's ``serializer_class``.
+If you wish to use a different serializer for serializing the results, set the
+:attr:`drf_haystack.mixins.FacetMixin.facet_objects_serializer_class` class attribute to whatever serializer you want
+to use, or override the :meth:`drf_haystack.mixins.FacetMixin.get_facet_objects_serializer_class` method.
+
+**Example faceted results with paginated serialized objects**
+
+.. code-block:: json
+
+ {
+ "fields": {
+ "firstname": [
+ {"...": "..."}
+ ],
+ "lastname": [
+ {"...": "..."}
+ ]
+ },
+ "dates": {
+ "created": [
+ {"...": "..."}
+ ]
+ },
+ "queries": {},
+ "objects": {
+ "count": 3,
+ "next": "http://example.com/api/v1/search/facets/?page=2&selected_facets=firstname_exact%3AJohn",
+ "previous": null,
+ "results": [
+ {
+ "lastname": "Baker",
+ "firstname": "John",
+ "full_name": "John Baker",
+ "text": "John Baker\n"
+ },
+ {
+ "lastname": "McClane",
+ "firstname": "John",
+ "full_name": "John McClane",
+ "text": "John McClane\n"
+ }
+ ]
+ }
+ }
+
+
+
+Setting up the view
+-------------------
+
+Any view that inherits the :class:`drf_haystack.mixins.FacetMixin` will have a special
+`action route <http://www.django-rest-framework.org/api-guide/viewsets/#marking-extra-actions-for-routing>`_ added as
+``^<view-url>/facets/$``. This view action will not care about regular filtering but will by default use the
+``HaystackFacetFilter`` to perform filtering.
+
+.. note::
+
+ In order to avoid confusing the filtering mechanisms in Django Rest Framework, the ``FacetMixin``
+ class has a couple of hooks for dealing with faceting, namely:
+
+ - :attr:`drf_haystack.mixins.FacetMixin.facet_filter_backends` - A list of filter backends that will be used to
+ apply faceting to the queryset. Defaults to :class:drf_haystack.filters.HaystackFacetFilter`, which should be
+ sufficient in most cases.
+ - :attr:`drf_haystack.mixins.FacetMixin.facet_serializer_class` - The :class:`drf_haystack.serializers.HaystackFacetSerializer`
+ instance that will be used for serializing the result.
+ - :attr:`drf_haystack.mixins.FacetMixin.facet_objects_serializer_class` - Optional. Set to the serializer class
+ which should be used for serializing faceted objects. If not set, defaults to ``self.serializer_class``.
+ - :attr:`drf_haystack.mixins.FacetMixin.filter_facet_queryset()` - Works exactly as the normal
+ :meth:`drf_haystack.generics.HaystackGenericAPIView.filter_queryset` method, but will only filter on
+ backends in the ``self.facet_filter_backends`` list.
+ - :meth:`drf_haystack.mixins.FacetMixin.get_facet_serializer_class` - Returns the ``self.facet_serializer_class``
+ class attribute.
+ - :meth:`drf_haystack.mixins.FacetMixin.get_facet_serializer` - Instantiates and returns the
+ :class:`drf_haystack.serializers.HaystackFacetSerializer` class returned from
+ :meth:`drf_haystack.mixins.FacetMixin.get_facet_serializer_class` method.
+ - :meth:`drf_haystack.mixins.FacetMixin.get_facet_objects_serializer` - Instantiates and returns the serializer
+ class which will be used to serialize faceted objects.
+ - :meth:`drf_haystack.mixins.FacetMixin.get_facet_objects_serializer_class` - Returns the
+ ``self.facet_objects_serializer_class``, or if not set, the ``self.serializer_class``.
+
+
+In order to set up a view which can respond to regular queries under ie ``^search/$`` and faceted queries under
+``^search/facets/$``, we could do something like this.
+
+We can also change the query param text from ``selected_facets`` to our own choice like ``params`` or ``p``. For this
+to make happen please provide ``facet_query_params_text`` attribute as shown in the example.
+
+.. code-block:: python
+
+ class SearchPersonViewSet(FacetMixin, HaystackViewSet):
+
+ index_models = [MockPerson]
+
+ # This will be used to filter and serialize regular queries as well
+ # as the results if the `facet_serializer_class` has the
+ # `serialize_objects = True` set.
+ serializer_class = SearchSerializer
+ filter_backends = [HaystackHighlightFilter, HaystackAutocompleteFilter]
+
+ # This will be used to filter and serialize faceted results
+ facet_serializer_class = PersonFacetSerializer # See example above!
+ facet_filter_backends = [HaystackFacetFilter] # This is the default facet filter, and
+ # can be left out.
+ facet_query_params_text = 'params' #Default is 'selected_facets'
+
+
+Narrowing
+---------
+
+As we have seen in the examples above, the ``HaystackFacetSerializer`` will add a ``narrow_url`` attribute to each
+result it serializes. Follow that link to narrow the search result.
+
+The ``narrow_url`` is constructed like this:
+
+ - Read all query parameters from the request
+ - Get a list of ``selected_facets``
+ - Update the query parameters by adding the current item to ``selected_facets``
+ - Pop the :attr:`drf_haystack.serializers.HaystackFacetSerializer.paginate_by_param` parameter if any in order to
+ always start at the first page if returning a paginated result.
+ - Return a ``serializers.Hyperlink`` with URL encoded query parameters
... 6546 lines suppressed ...
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/drf-haystack.git
More information about the Python-modules-commits
mailing list