Django Haystack Results to Django Rest Framework Serializer
Asked Answered
G

1

8

I am attempting to do a search using Django Haystack and then upon retrieving results I need to pass these results to my Django Rest Framework serializer.

The Django Rest Framework serializers.ModelSerializer requires that a queryset of objects gets sent for the serializer to be able to serialize these objects along with their database fields.

When I create my API view and use search to get results haystack returns a searchqueryset.

How could I get this searchqueryset into a django queryset without doing something like:

article_queryset = Article.objects.filter(id__in=[i.object for i in searchqueryset])

As you could imagine, sometimes search can return excess of 1000 search results which means that the above would be very inefficient.

Right now the Django rest framework allows me to paginate my returned objects. I am paginating by 30 objects on each page. How would it be possible for me to do the same with my Haystack searchqueryset?

Any advice or ideas on how to use Haystack along with Django Rest Framework would be great. Examples of how others have done a similar thing would be cool too :)

Godart answered 15/11, 2013 at 14:24 Comment(1)
What search backend are you using? If your using elasticsearch, your probably taking an extra step you don't need to, because in elasticsearch results already come back in json form in the _source field.Velure
M
8

You could use Haystack's SearchView (instead of a DRF view) which exposes the page object for pagination. Then you can just pass that to your serializer.

E.G. Awhile back I was working on something that required the json object of the rendered results on the current HTML page, so I wrote a filter tag to do this (we were using FacetedSearchView for the UI, and DRF for a more general purpose RESTful API). Called like so in the template:

var articlesJson = {{ page.object_list|article_jsonify }}

Actual filter tag:

import collections
from django.template import Library
from django.utils.safestring import mark_safe
from rest_framework.renderers import JSONRenderer

register = Library()


def article_jsonify(object):
    if isinstance(object, collections.Iterable):
        many = True
        ids = [obj.object.id for obj in object]
    else:
        many = False
        ids = [object.id]

    articles = Article.objects.filter(pk__in=ids)
    serializer = ArticleSerializer(articles, many=many)
    content = JSONRenderer().render(serializer.data)
    return mark_safe(content)

register.filter('article_jsonify', article_jsonify)

You could also write a view inherited from generics.ListAPIView and override the get_queryset method, where you would pass the request's query parameter to a SearchQuerySet, then output it using the Serializer. More info here:

http://www.django-rest-framework.org/api-guide/filtering

Of course you might not be able to use the ModelSerializer this way, unless you do something like you mentioned. However DRF has an example on using the paginator on a queryset like so:

http://www.django-rest-framework.org/api-guide/pagination#paginating-querysets

UPDATE I ended up eventually using a DRF view that uses a Haystack SearchQuerySet as the queryset, and then passing it to a Paginator. Here is a simplified example (I'm sure it can be streamlined some), but I hope it helps someone gets started.

class ArticleList(ListCreateAPIView):

    """
    List, Create files
    """
    model = Article

    def get(self, request, *args, **kwargs):
        # simplified filtering of an SQS
        q = request.get('q', '')
        sqs = SearchQuerySet().filter(content=Clean(q))
        paginator = Paginator(sqs, 20)

        page = request.QUERY_PARAMS.get('page')
        try:
            articles = paginator.page(page)
        except PageNotAnInteger:
            # If page is not an integer, deliver first page
            articles = paginator.page(1)
        except PageNotAnInteger:
            # If page is out of range, deliver last page
            articles = paginator.page(paginator.num_pages)

        serializer_context = {'request': request}
        serializer = PaginatedArticleSerializer(articles, context=serializer_context)

        return Response(serializer.data)

class ArticleSerializer(serializers.ModelSerializer):

    """
    Base Article Serializer
    """

    class Meta:
        model = Article

class PaginatedArticleSerializer(pagination.PaginationSerializer):

    """
    Serializes page objects of article querysets.
    """
    start_index = serializers.SerializerMethodField('get_start_index')
    end_index = serializers.SerializerMethodField('get_end_index')
    num_pages = serializers.Field(source='paginator.num_pages')

    class Meta:
        object_serializer_class = ArticleSerializer

    def get_start_index(self, page):
        return page.start_index()

    def get_end_index(self, page):
        return page.end_index()

    def get_curr_page(self, page):
        return page.number
Middlemost answered 12/1, 2014 at 23:5 Comment(2)
I do not understand how this code serializes instances of Haystacks SearchResults using a serializer that expects instances of the Article model. I'm trying to implement a simple version of this for a simple api endpoint that will output JSON containing serialized model instances associated with the search resulsts.Withdraw
SearchResult and SearchQuerySet are modeled (no pun intended) after Django's Model and Queryset classes, so they have similar behaviors and interfaces. I believe it just worked for me, I'll look and let you know if I had to do anything special in the serializer.Middlemost

© 2022 - 2024 — McMap. All rights reserved.