django

Поиск в Django REST Framework с помощью Elasticsearch

  • вторник, 2 мая 2017 г. в 03:14:09
https://habrahabr.ru/post/327694/
  • Python
  • Django


Пользователи ищут товары в интернет-магазине, ищут стати, поиск это неотъемлемый компонент сайта. Быстрый и гибкий поиск сложно реализовать средствами реляционных баз данных. Для таких задач используют поисковые движки, один из которых Elasticsearch. Elasticsearch хорошо документирован и доступен из коробки на AWS.


Для работы с elasticsearch используется библиотека elasticsearch-py или elasticsearch-dsl-py. elasticsearch-dsl-py это надстройка над elasticsearch-py, она проста в использовании и поддерживает elasticsearch версии 5.x. На базе этой библиотеки была создана библиотека django-rest-elasticsearch, которая основана на идеологии существующего поиска в Django REST Framework. Ниже я детально распишу как реализовать поиск в Django REST Framework с помощью elasticsearch используя данную библиотеку.



В качестве примера рассмотрим реализацию простого блога с фильтрацией по тегам и поиском по заголовку статей.


Установка


Процесс установки elasticsearch и django детально расписан в официальных документациях. С установкой пакета все достаточно просто


pip install django-rest-elasticsearch

Создание модели и индекса


Создадим модель


class Blog(models.Model):
    title = models.CharField(_('Title'), max_length=1000)
    created_at = models.DateTimeField(_('Created at'), auto_now_add=True)
    body = models.TextField(_('Body'))
    tags = ArrayField(models.CharField(max_length=200), blank=True, null=True)
    is_published = models.BooleanField(_('Is published'), default=False)

    def __str__(self):
        return self.title

После создания модели опишем нашу модель в виде elasticsearch документа.


class BlogIndex(DocType):

    pk = Integer()
    title = Text(fields={'raw': Keyword()})
    created_at = Date()
    body = Text()
    tags = Keyword(multi=True)
    is_published = Boolean()

    class Meta:
        index = 'blog'

Теперь можно создать индекс в elasticsearch


BlogIndex.init()

Автоматическое обновление документов в elasticsearch


После создания модели и индекса, нужно что бы любые изменения в нашей модели были отображены в elasticsearch. Наилучшим средством это сделать добавить django сигнал, который отправить уведомления когда будут происходит изменения в модели. Прежде чем создать сигнал создадим сериалайзер для преобразования django объекта в документ elasticsearch


from rest_framework_elasticsearch.es_serializer import ElasticModelSerializer
from .models import Blog
from .search_indexes import BlogIndex

class ElasticBlogSerializer(ElasticModelSerializer):
    class Meta:
        model = Blog
        es_model = BlogIndex
        fields = ('pk', 'title', 'created_at', 'tags', 'body', 'is_published')

Теперь добавим сигнал


from django.db.models.signals import pre_save, post_delete
from django.dispatch import receiver
from .serializers import Blog, ElasticBlogSerializer

@receiver(pre_save, sender=Blog, dispatch_uid="update_record")
def update_es_record(sender, instance, **kwargs):
    obj = ElasticBlogSerializer(instance)
    obj.save()

@receiver(post_delete, sender=Blog, dispatch_uid="delete_record")
def delete_es_record(sender, instance, *args, **kwargs):
    obj = ElasticBlogSerializer(instance)
    obj.delete(ignore=404)

После добавления сигнала, любые изменения в модели моментально будут сделаны в elasticsearch


Создание view


Приступим к создания view для поиска и фильтрации


from elasticsearch import Elasticsearch, RequestsHttpConnection
from rest_framework_elasticsearch import es_views, es_filters
from .search_indexes import BlogIndex

class BlogView(es_views.ListElasticAPIView):
    es_client = Elasticsearch(hosts=['elasticsearch:9200/'],
                              connection_class=RequestsHttpConnection)
    es_model = BlogIndex
    es_filter_backends = (
        es_filters.ElasticFieldsFilter,
        es_filters.ElasticSearchFilter
    )
    es_filter_fields = (
        es_filters.ESFieldFilter('tag', 'tags'),
    )
    es_search_fields = (
        'tags',
        'title',
    )

Вот и все, примеры поиска


http://example.com/blogs/api/list?search=elasticsearch
http://example.com/blogs/api/list?tag=opensource
http://example.com/blogs/api/list?tag=opensource,aws

Полный код примера доступен на github. Надеюсь что статья поможет Вам реализовать поиск в Вашем проекте.