탄력적 검색 + 장고

요구 사항:


  • 장고
  • 탄력적 검색Install(필수 버전 7)
  • Drf Haystack
  • PoetryInstall 또는 pip 또는 pipenv
  • 를 사용할 수 있습니다.



    프로젝트 설정:




    $ 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==^7.x.x
    $ 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")),
    ]
    
    

    메인/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.elasticsearch7_backend.Elasticsearch7SearchEngine',
            "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 파일을 만듭니다.

    #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를 구축하려면 indexes.SearchIndex 및 indexes.Indexable을 하위 클래스로 만들고 데이터를 저장할 필드를 정의하고 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)
    
    

    so you can create a each class of serializers for each models Like this.



    # searches/urls.py
    
    from django.urls import path
    
    from .views import AggregateSearchViewSet
    
    
    urlpatterns = [
    path("hotels/search/", AggregateSearchViewSet.as_view())
    ]
    
    

    검색 앱 내에 템플릿 디렉토리를 만듭니다.
    템플릿 폴더는 다음과 같습니다.

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

    마지막으로 앱을 마이그레이션하고 수퍼유저를 생성하고 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"
            }
        ]
    




    리야마 / elastic_search_dj_예제


    Django + haystack + 탄력적 검색의 예

    좋은 웹페이지 즐겨찾기