Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Faceting #11

Closed
rhblind opened this issue Jun 14, 2015 · 3 comments
Closed

Add support for Faceting #11

rhblind opened this issue Jun 14, 2015 · 3 comments
Assignees

Comments

@rhblind
Copy link
Owner

rhblind commented Jun 14, 2015

We should support faceting.
This should probably be implemented as a FacetingFilter, which picks up and filters on faceting queries like narrowing.

https://django-haystack.readthedocs.org/en/latest/faceting.html#faceting

@rhblind
Copy link
Owner Author

rhblind commented Sep 30, 2015

Suggestions for solution

HaystackFacetFilter

  • Read fields, exclude and a special field_options from view.serializer_class.Meta
  • Read and parse query parameters to override field_options.
  • From field_options; figure out which is date_facets, field_facets and query_facets.
  • Apply facets to SearchQuerySet and return it as a queryset. Filters needs to be chainable, therefore it must return a regular SearchQuerySet

Example field_options:

field_options = {
    "firstname": {},
    "lastname": {},
    "created": {
        "start_date": datetime.now() - timedelta(days=3 * 365),
        "end_date": datetime.now(),
        "gap_by": "day",
        "gap_amount": 10
    }
}

field_options can be overridden by adding to query string in the following format:

fieldname=option1:value1,option2:value2

/api/v1/search/?firstname=limit:100&created=start_date:21. Jan 2015,gap_by:day

FacetSerializer (or FacetSerializerMixin)

  • Have dates, fields and queries as default fields.
  • Needs to have a SearchQuerySet().facet_counts() as its initial data
  • Each field should serialize like below
{
    "dates": [
        "created": [
            {
                "text": "2015-05-15T21:08:35",
                "count": 76,
                "narrow": "https://example.com/api/v1/search/dates/created/?text=2015-05-15T21:08:35"
            },
            {
                "text": "2015-06-12T23:14:10",
                "count": 29,
                "narrow": "https://example.com/api/v1/search/dates/created/?text=2015-06-12T23:14:10"
            }
        ]
    ],
    "fields": [
        "firstname": [
            {
                "text": "John",
                "count": 26,
                "narrow": "https://example.com/api/v1/search/fields/firstname/?text=John"
            },
            {
                "text": "Randy",
                "count": 13,
                "narrow": "https://example.com/api/v1/search/fields/firstname/?text=Randy"
            }
        ]
    ],
    "queries": [
        ...
    ]
}

The narrow url must be injected per result. The format however is open for debate.

FacetViewMixin

  • Can override list() method in order to feed the SearchQuerySet().facet_counts() as initial data to the serializer_class.
  • Provide a @list_route action for narrowing results
  • Should take care of parsing the selected_facets from query parameter

@rhblind rhblind self-assigned this Sep 30, 2015
rhblind added a commit that referenced this issue Oct 3, 2015
@rhblind
Copy link
Owner Author

rhblind commented Oct 3, 2015

This is basically done and pushed to development branch. Some missing features such as narrowing, but will open a dedicated Issue for that

@hubert10
Copy link

hubert10 commented Sep 1, 2016

Hello Sir?
I have been working with drf haystack elasticsearch and according to the haystack latest documentation, it seems for faceting and more-like-this working, but for me it doesn't.
I am using

elasticsearch==2.4.0
Django==1.10
djangorestframework==3.4.6
django-haystack==2.5.dev1
drf-haystack==1.6.0rc2

serializers and views:
for faceting

class PersonFacetSerializer(HaystackFacetSerializer):
    serialize_objects = True  
    class Meta:
        index_classes = [PersonIndex]
        fields = ["firstname", "lastname", "created"]
        field_options = {
            "firstname": {},
            "lastname": {},
            "created": {
                "start_date": datetime.datetime.now() - timedelta(days=3 * 365),
                "end_date": datetime.datetime.now(),
                "gap_by": "month",
                "gap_amount": 3
            }
        }
class PersonSearchViewSet(FacetMixin, HaystackViewSet):
    index_classes = [PersonIndex]
    serializer_class = PersonSerializer
    facet_filter_backends = [HaystackFacetFilter]
    filter_backends = [HaystackFilter]
    facet_serializer_class = PersonFacetSerializer

for more_like_this

class PersonSerializer(HaystackSerializer):
    more_like_this = serializers.HyperlinkedIdentityField(view_name="search-person-mlt-more-like-this", read_only=True)
    class Meta:
        index_classes = [PersonIndex]
        fields = ["firstname", "lastname"]
class PersonViewSet(MoreLikeThisMixin, HaystackViewSet):
    index_models = [Person]
    serializer_class = PersonSerializer

urls:

routerV2 = routers.DefaultRouter()
router = routers.DefaultRouter()

routerV2.register("person", viewset=PersonSearchView, base_name="Person-facet-search")
router.register("person", viewset=PersonViewSet, base_name="search-more-like-this")
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^', include('backend.urls')),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
    url(r'^api/v2/', include(routerV2.urls)),
    url(r'^search', include('haystack.urls')),
    url(r'^', include(routerV2.urls)),
    url(r'^', include('haystack.urls')),
    url(r'^facets', include(routerV2.urls))
]+static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

queries

http://localhost:8000/api/v2/person/?firstname=hubert
GET /api/v2/person/?firstname=hubert
HTTP 200 OK
  Allow: GET, HEAD, OPTIONS
  Content-Type: application/json
  Vary: Accept

  {
    "count": 3,
    "next": null,
    "previous": null,
    "results": [
        {
            "text": "\nDiallo\nhubert\nOukam\n\n",
            "firstname": "hubert",
            "lastname": "Diallo",
            "city": "Oukam",
            "highlighted": "...<em class=\"highlighted\">hubert</em>\nOukam\n\n"
        },
        {
            "text": "\nkanyamahanga\nhubert\nOukam\n\n",
            "firstname": "hubert",
            "lastname": "kanyamahanga",
            "city": "Oukam",
            "highlighted": "...<em class=\"highlighted\">hubert</em>\nOukam\n\n"
        },
        {
            "text": "\nNiang\nkeba hubert\nFoar\n\n",
            "firstname": "keba hubert",
            "lastname": "Niang",
            "city": "Foar",
            "highlighted": "...<em class=\"highlighted\">hubert</em>\nFoar\n\n"
        }
    ]
}

GET /person/?firstname=hubert
HTTP 200 OK
  Allow: GET, HEAD, OPTIONS
  Content-Type: application/json
  Vary: Accept

  {
    "count": 3,
    "next": null,
    "previous": null,
    "results": [
        {
            "firstname": "hubert",
            "lastname": "Diallo",
            "more_like_this": null
        },
        {
            "firstname": "hubert",
            "lastname": "kanyamahanga",
            "more_like_this": null
        },
        {
            "firstname": "keba hubert",
            "lastname": "Niang",
            "more_like_this": null
        }
    ]
}

Please if there is what I what I am missing, any help, guide or suggestion will be welcomed!

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants