Рубрики
Без рубрики

Эластичный поиск + django

Требования: Установка Django Elastic Search (требуется версия 6) DRF Haystack Poetry Inst … С тегом Джанго, Python, Elasticsearch, Haystack.

$ mkdir dj_elastic && cd dj_elastic
$ python3 -m venv env
$ source env/bin/activate
$ poetry init
$ poetry add django djangorestframework django-autoslug black isort
$ poetry add django-haystack drf-haystack
$ poetry add elasticsearch==^5.0
$ django-admin.py startproject main
$ python manage.py startapp searches
$ python manage.py startapp commons

Справочник проекта должен выглядеть как:

── dj_elastic
├── main
│ ├── **init**.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── manage.py
└── commons
└── searches

Основное приложение/url.py

from django.contrib import admin
from django.urls import path
from django.urls.conf import include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("api/v1/", include("searches.urls")),
]

main/settings.py

INSTALLED_APPS = [
    "searches",
    "commons",
    "haystack",
    "rest_framework",
]
TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [BASE_DIR / "templates"],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
        },
    },
]
HAYSTACK_CONNECTIONS = {
    "default": {
        "ENGINE": "haystack.backends.elasticsearch5_backend.Elasticsearch5SearchEngine",
        "URL": "http://127.0.0.1:9200/",
        "INDEX_NAME": "haystack",
    },
}

HAYSTACK_SIGNAL_PROCESSOR = "haystack.signals.RealtimeSignalProcessor"

👏🏻 Отлично, закончено с основными настройками …. Далее давайте создадим модели. Перейдите к Commons/Models.py

# commons/models.py

from django.db import models
from autoslug import AutoSlugField
from django.contrib.auth.models import User
from django.utils.translation import gettext_lazy as _


def slugify(value):
    return value.replace(" ", "-").lower()


class ConfigChoiceCategory(models.Model):

    name = models.CharField(
        _("Config Choice Category Name"),
        help_text=_("Required and Unique"),
        max_length=255,
        unique=True,
    )
    slug = AutoSlugField(
        verbose_name=_("Config Choice Category Slug"),
        populate_from="name",
        slugify=slugify,
    )
    entered_by = models.ForeignKey(User, blank=True, on_delete=models.CASCADE)
    is_active = models.BooleanField(default=True)

    class Meta:
        verbose_name = _("Config Choice Category")
        verbose_name_plural = _(" Config Choice Categories")

    def __str__(self):
        return self.name


class ConfigChoice(models.Model):
    name = models.CharField(
        _("Config Choice Name"),
        help_text=_("Required and Unique"),
        max_length=255,
        unique=True,
    )
    description = models.TextField()
    slug = AutoSlugField(
        verbose_name=_("Config Choice Slug"),
        populate_from="name",
        slugify=slugify,
    )
    config_choice_category = models.ForeignKey(
        ConfigChoiceCategory, on_delete=models.CASCADE
    )
    entered_by = models.ForeignKey(User, on_delete=models.CASCADE)

    class Meta:
        verbose_name = _("Config Choice")
        verbose_name_plural = _("Config Choices")

    def __str__(self) -> str:
        return self.name


class Address(models.Model):
    street_1 = models.CharField(max_length=200)
    street_2 = models.CharField(max_length=200, null=True, blank=True)
    city = models.CharField(max_length=100)
    state = models.CharField(max_length=100)
    zip_code = models.CharField(max_length=100)
    country = models.CharField(max_length=50)
    latitude = models.FloatField()
    longitude = models.FloatField()

    def __str__(self):
        return f"{self.street_1}, {self.city}, {self.state}, {self.country}"``

Мы тут:

  • Создал модели Configchoicecategory и Configchoice , где configchoice имеет отношение с Configchoicecategory Анкет
  • И у нас есть Адресная модель слишком

Зарегистрировать модели на admin.py

from django.contrib import admin

# Register your models here.

from .models import (
    Address,
    ConfigChoice,
    ConfigChoiceCategory,
)


admin.site.register(ConfigChoiceCategory)
admin.site.register(ConfigChoice)
admin.site.register(Address)


Итак, давайте перейдем к Поиск Приложение и создайте модели для отелей.

#searches/models.py

from commons.models import Address, ConfigChoice
from django.db import models
from django.utils.translation import gettext_lazy as _

from autoslug import AutoSlugField


def slugify(value):
    return value.replace(" ", "-").lower()


class CoreModel(models.Model):

    created = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True


class HotelType(models.Model):
    name = models.CharField(_("Hotel Types Name"), max_length=255)

    class Meta:
        verbose_name = _("Hotel Type")
        verbose_name_plural = _("Hotel Types")

    def __str__(self) -> str:
        return self.name


class HotelSpecifications(models.Model):
    hotel_type = models.ForeignKey(HotelType, on_delete=models.RESTRICT)
    name = models.CharField(_("Hotel Spec Name"), max_length=255)

    class Meta:
        verbose_name = _("Hotel Specification")
        verbose_name_plural = _("Hotel Specifications")

    def __str__(self) -> str:
        return f"{self.name}"


class Hotel(CoreModel):
    name = models.CharField(_("Hotel Name"), max_length=50)
    description = models.TextField(_("Hotel Descriptions"), default="")
    hotel_type = models.ForeignKey(HotelType, on_delete=models.CASCADE)
    slug = AutoSlugField(
        verbose_name=_("Hotel Slug"),
        populate_from="name",
        slugify=slugify,
    )
    is_active = models.BooleanField(default=True)
    config_choice = models.ForeignKey(ConfigChoice, on_delete=models.RESTRICT)

    class Meta:
        verbose_name = _("Hotel")
        verbose_name_plural = _("Hotels")

    def get_absolute_url(self):
        return f"/{self.slug}/"

    def __str__(self) -> str:
        return self.name


class HotelSpecificationValue(models.Model):
    hotel = models.ForeignKey(Hotel, on_delete=models.CASCADE)
    specification = models.ForeignKey(HotelSpecifications, on_delete=models.RESTRICT)
    value = models.CharField(
        _("Value"),
        max_length=255,
        help_text=_("Hotel specification value (maximum of 255 words"),
    )

    class Meta:
        verbose_name = _("Hotel Specification Value")
        verbose_name_plural = _("Hotel Specification Values")

    def __str__(self):
        return self.value


class HotelImage(CoreModel):
    hotel = models.ForeignKey(
        Hotel, on_delete=models.CASCADE, related_name="hotel_image"
    )
    image_urls = models.URLField(
        _("Hotel Image URLs"),
        help_text=_("Images Urls"),
    )
    caption = models.CharField(
        verbose_name=_("Alternative text"),
        help_text=_("Please add alturnative text"),
        max_length=255,
        null=True,
        blank=True,
    )
    is_feature = models.BooleanField(default=False)

    class Meta:
        verbose_name = _("Hotel Image")
        verbose_name_plural = _("Hotel Images")


class HotelAddress(models.Model):
    hotel = models.ForeignKey(
        Hotel, on_delete=models.CASCADE, related_name="hotel_address"
    )
    address = models.ForeignKey(Address, on_delete=models.CASCADE)

    def __str__(self):
        return f"{self.hotel.name} {self.address.city}"

Регистрация моделей в admin.py

from django.contrib import admin

from .models import (
    Hotel,
    HotelImage,
    HotelSpecifications,
    HotelSpecificationValue,
    HotelType,
    HotelAddress,
)


class HotelSpecificationInline(admin.TabularInline):
    model = HotelSpecifications


@admin.register(HotelType)
class HotelTypeAdmin(admin.ModelAdmin):
    inlines = [
        HotelSpecificationInline,
    ]


class HotelImageInline(admin.TabularInline):
    model = HotelImage


class HotelSpecificationValueInline(admin.TabularInline):
    model = HotelSpecificationValue


@admin.register(Hotel)
class HotelAdmin(admin.ModelAdmin):
    inlines = [HotelSpecificationValueInline, HotelImageInline]


admin.site.register(HotelAddress)

Создайте файл search_indexes.py Inside Searchs App.

#searches/search_indexes.py

from django.utils import timezone
from haystack import indexes

from .models import Hotel, HotelAddress, HotelImage, HotelSpecificationValue


class HotelIndex(indexes.SearchIndex, indexes.Indexable):
    text = indexes.CharField(document=True, use_template=True)
    name = indexes.CharField(model_attr="name")
    hotel_type = indexes.CharField(model_attr="hotel_type")
    config_choice = indexes.CharField(model_attr="config_choice")
    autocomplete = indexes.EdgeNgramField()

    @staticmethod
    def prepare_autocomplete(obj):
        return " ".join((obj.name, obj.hotel_type.name, obj.config_choice.name))

    def get_model(self):
        return Hotel


class HotelSpecIndex(indexes.SearchIndex, indexes.Indexable):
    text = indexes.CharField(document=True, use_template=True)
    value = indexes.CharField(model_attr="value")

    def get_model(self):
        return HotelSpecificationValue


class HotelImageIndex(indexes.SearchIndex, indexes.Indexable):
    text = indexes.CharField(document=True, use_template=True)
    image_urls = indexes.CharField(model_attr="image_urls")
    caption = indexes.CharField(model_attr="caption")

    def get_model(self):
        return HotelImage


class HotelAddressIndex(indexes.SearchIndex, indexes.Indexable):
    text = indexes.CharField(document=True, use_template=True)
    address = indexes.CharField(model_attr="address")

    def get_model(self):
        return HotelAddress
  • Создает уникальный SearchIndex Для каждого типа модели вы хотите индексировать, хотя вы можете повторно использовать один и тот же SearchIndex между различными моделями, если вы позаботитесь при этом, и имена ваших поля очень стандартизированы.
  • Построить SearchIndex , все, что необходимо, – это подкласс оба индексы. SearchIndex & индексы. Индексируемый , Определите поля, с которыми вы хотите сохранить данные, и определите метод GET_MODEL.

Сериализация и взгляды:

#searches/serializers.py

from drf_haystack.serializers import HaystackSerializer

from .search_indexes import (
    HotelIndex,
    HotelSpecIndex,
    HotelImageIndex,
    HotelAddressIndex,
)


class AggregateSerializer(HaystackSerializer):
    class Meta:
        index_classes = [HotelIndex, HotelSpecIndex, HotelImageIndex, HotelAddressIndex]
        fields = [
            "name",
            "hotel",
            "config_choice",
            "value",
            "image_urls",
            "caption",
            "address",
            "autocomplete",
        ]

# searches/serializers.py

from .serializers import AggregateSerializer
from rest_framework.mixins import ListModelMixin
from drf_haystack.generics import HaystackGenericAPIView


class AggregateSearchViewSet(ListModelMixin, HaystackGenericAPIView):

    serializer_class = AggregateSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

Таким образом, вы можете создать каждый класс сериалов для каждой модели Как это Анкет

# searches/urls.py

from django.urls import path

from .views import AggregateSearchViewSet


urlpatterns = [
path("hotels/search/", AggregateSearchViewSet.as_view())
]

Создайте каталог шаблонов внутри приложения Searches. Папка шаблонов будет выглядеть так:

templates
    ├── search
        ├── indexes
            ├── searches
                ├── hotel_text.txt
                ├── hoteladdress_text.txt
                ├── hotelimage_text.txt
                ├── hotelspecificationvalue_text.txt

Наконец мигрировать Ваши приложения, CreateSuperuser и добавьте данные о отелях, используя панели администратора Django.

Просто беги ./manage.py Rebuild_index. Вы получите некоторые итоги того, сколько моделей было обработано и помещено в индекс.

Теперь, когда у нас есть представление, мы можем начать его использовать. По умолчанию класс Haystackgenericapiview настроен на использование Haystackfilter. Это самый базовый фильтр, включенный и может выполнять базовый поиск, запрашивая любое поле, включенное в атрибут поля на сериализаторе.

http://127.0.0.1:8000/api/v1/hotels/search/

[
    {
        "image_urls": "https://images.moviesanywhere.com/8ccb2868a61ac0612d780eb3b18e5220/6fda2dc9-a774-4ba6-9e80-679accfcc8ed.jpg?h=375&resize=fit&w=250",
        "caption": "img"
    },
    {
        "name": "Transylvania Hotal",
        "config_choice": "Active",
        "autocomplete": "Transylvania Hotal 3 Star Active"
    },
    {
        "value": "12 AD"
    },
    {
        "value": "Monsters Hotel"
    },
    {
        "address": "US"
    },
    {
        "value": "12 AD"
    },
    {
        "value": "gogogog"
    },
    {
        "image_urls": "https://images.moviesanywhere.com/8ccb2868a61ac0612d780eb3b18e5220/6fda2dc9-a774-4ba6-9e80-679accfcc8ed.jpg?h=375&resize=fit&w=250",
        "caption": "img"
    },
    {
        "value": "lONG LONG TIME AGO"
    },
    {
        "name": "demo",
        "config_choice": "Active",
        "autocomplete": "demo 3 Star Active"
    },
    {
        "value": "lONG LONG TIME AGO"
    }
]


http://127.0.0.1:8000/api/v1/hotels/search/?name="demo"

"results": [
        {
            "name": "demo",
            "config_choice": "Active",
            "autocomplete": "demo 3 Star Active"
        }
    ]

lyamaa/elastic_search_dj_example

Примеры Django + Hay Stack + Elastic Search

Оригинал: “https://dev.to/lymaa/elastic-search-django-g85”