برنامه نویسی

جستجوی کامل متن: ایجاد یک فیلتر پشتی برای چارچوب استراحت جنگو

متن Full-Text Search: Implementando com Postgres e Django [1] نظرات در مورد اجرای جستجوی تمام متن انجام دادن Postgres، توسط Leandro Proença در متن آورده شده است A powerful full-text search in PostgreSQL in less than 20 lines [2]، با استفاده از جنگو.

این پروژه در GitHub است [3] و برای تکمیل آن، این متن با هدف ایجاد یک فیلتر پشتی، یعنی a آداپتور فیلتر، برای مقابله با جستجوی تمام متنهمانطور که در الگوریتم متن قبلی در داخل استراحت-چارچوب.

برای اینکه بتوانیم این پشتیبانی را به بهترین شکل ممکن اضافه کنیم، می توانیم a فیلتر پشتی سفارشی شده به عنوان یک مرجع، SearchFilter انجام اصلی جنگو [4] ه [5].


کد را به من نشان بده

کد توسعه یافته در این متن در مخزن django-full-text-search در Github موجود است.


پیاده سازی BaseFilterBackend

برای ایجاد backend فیلتر، باید کلاس را پیاده سازی کنید rest_framework.filters.BaseFilterBackend:

from rest_framework.filters import BaseFilterBackend

class FullTextSearchFilter(BaseFilterBackend):
    pass
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

گرفتن پارامترها

اولین متدهایی که در کلاس فوق پیاده سازی می شوند، فقط متدهایی هستند که به دنبال ویژگی هایی در درخواست هستند، مانند پارامتر. ?search، یا نه ModelViewSet مانند، برای مثال، search_fields. این کد بسیار شبیه به کد موجود در مرجع در است [5]:

from rest_framework.filters import BaseFilterBackend
from rest_framework.settings import api_settings

class FullTextSearchFilter(BaseFilterBackend):
    search_param = api_settings.SEARCH_PARAM

    def get_config(self, view, request):
        return getattr(view, "search_config", None)

    def get_search_fields(self, view, request):
        return getattr(view, "search_fields", None)

    def get_similarity_threshold(self, view, request):
        return getattr(view, "similarity_threshold", 0)

    def get_search_term(self, request):
        params = request.query_params.get(self.search_param, '')
        params = params.replace('\x00', '')  # strip null characters
        params = params.replace(',', ' ')
        return params
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

انجام جستجو

مهمترین متد این کلاس بدون شک the filter_queryset که روشی است که در a تغییرات ایجاد می کند queryset برای برگرداندن پاسخ API.

قبل از هر چیز لازم است پارامترهایی را برای انجام جستجوی خود از طریق روش های اجرا شده در بالا بدست آوریم:

def filter_queryset(self, request, queryset, view):
    search_fields = self.get_search_fields(view, request)
    search_term = self.get_search_term(request)
    config = self.get_config(view, request)
    threshold = self.get_similarity_threshold(view, request)
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اولین نکته ای که باید مورد توجه قرار گیرد این است که اگر متغیر search_fields شما search_term پر نشده است، می توانیم آن را برگردانیم queryset بدون ایجاد تغییرات:

def filter_queryset(self, request, queryset, view):
    # ...

    if not search_term or not search_fields:
        return queryset
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بقیه روش بسیار شبیه به آنچه قبلاً در متن قبلی پیاده سازی کرده ایم است:

def filter_queryset(self, request, queryset, view):
    # ...

    search_vector = SearchVector(*search_fields, config=config)
    search_query = SearchQuery(search_term, config=config)

    queryset = queryset.annotate(
        search=search_vector,
        rank=SearchRank(
            search_vector,
            search_query,
        ),
        similarity=TrigramSimilarity(*search_fields, search_term),
    ).filter(
        Q(search=search_query) | Q(similarity__gt=threshold)
    ).order_by("-rank", "-similarity")

    return queryset
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ذکر این نکته ضروری است که search_fields در اینجا به عنوان استفاده می شود *search_fields تا آرایه را “واشکنی” کند. بنابراین، اگر search_fields = ["name", "description"]، ایجاد مصداق SearchVector به عنوان انجام خواهد شد SearchVector("name", "description", config=config).

در نهایت، کلاس کامل خواهد بود:

class FullTextSearchFilter(BaseFilterBackend):
    search_param = api_settings.SEARCH_PARAM

    def get_config(self, view, request):
        return getattr(view, "search_config", None)

    def get_search_fields(self, view, request):
        return getattr(view, "search_fields", None)

    def get_similarity_threshold(self, view, request):
        return getattr(view, "similarity_threshold", 0)

    def get_search_term(self, request):
        params = request.query_params.get(self.search_param, '')
        params = params.replace('\x00', '')  # strip null characters
        params = params.replace(',', ' ')
        return params

    def filter_queryset(self, request, queryset, view):
        search_fields = self.get_search_fields(view, request)
        search_term = self.get_search_term(request)
        config = self.get_config(view, request)
        threshold = self.get_similarity_threshold(view, request)

        if not search_term or not search_fields:
            return queryset

        search_vector = SearchVector(*search_fields, config=config)
        search_query = SearchQuery(search_term, config=config)

        queryset = queryset.annotate(
            search=search_vector,
            rank=SearchRank(
                search_vector,
                search_query,
            ),
            similarity=TrigramSimilarity(*search_fields, search_term),
        ).filter(
            Q(search=search_query) | Q(similarity__gt=threshold)
        ).order_by("-rank", "-similarity")

        return queryset
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید


با استفاده از FullTextSearchFilter

کلاس FullTextSearchFilter قابل استفاده در filter_backends از ModelViewSet انجام دادن django-rest-framework. به زبان ساده:

from rest_framework import serializers
from rest_framework.viewsets import ModelViewSet
from texto.models import Singer
from core.filters import FullTextSearchFilter

class SingerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Singer
        fields = "__all__"

class SingerViewSet(ModelViewSet):
    queryset = Singer.objects.all()
    serializer_class = SingerSerializer
    filter_backends = [FullTextSearchFilter]
    search_config = "portuguese"
    search_fields = ["name"]
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هنگام ثبت نام SingerViewSet ناس urls از پروژه، امکان برقراری تماس با این پروژه وجود دارد نقطه پایانی با استفاده از ?search مانند جستجوی تمام متن:

from django.urls import path, include
from rest_framework.routers import SimpleRouter
from .viewsets import SingerViewSet

router = SimpleRouter()
router.register("singer", SingerViewSet, "Singer")

urlpatterns = [
    path('api/', include(router.urls))
]
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نمونه ای از فراخوانی API با ?search=Marrone و نمایش نتایج فیلتر شده و مرتب شده به درستی


نمایش رتبه و شباهت در بازگشت API

حتی امکان نمایش داده های آن نیز وجود دارد rank ه similarity در بازگشت از API. از آنجایی که این داده ها در حال حاشیه نویسی، یعنی اضافه شدن در موجودیت هستند، تنها امکان تغییر آن وجود دارد ModelSerializer:

class SingerSerializer(serializers.ModelSerializer):
    rank = serializers.FloatField(read_only=True)
    similarity = serializers.FloatField(read_only=True)

    class Meta:
        model = Singer
        fields = "__all__"
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

فراخوانی مثال به API با ?search=Marrone و نمایش نتایج با فیلدهای رتبه و شباهت نمایش داده شده

اما بدون جستجو چطور؟

فقط اضافه کنید rank ه similarity نه ModelSerializer مشکل به ارمغان می آورد: وقتی که نقطه پایانی بدون نام خوانده می شود ?search داده های rank ه similarity برگردانده نمی شوند:

مثالی از بازگشت API بدون استفاده از پارامتر ?search در URL و اینکه موارد بدون فیلد رتبه و شباهت برگردانده می شوند.

این را می توان با افزودن پارامتر در سازنده FloatField حل کرد default=0:

class SingerSerializer(serializers.ModelSerializer):
    rank = serializers.FloatField(read_only=True, default=0)
    similarity = serializers.FloatField(read_only=True, default=0)

    class Meta:
        model = Singer
        fields = "__all__"
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید


فیلتر کردن بر اساس شباهت

در نهایت، برای فیلتر بر اساس شباهت، امکان تعریف متغیر وجود دارد similarity_threshold نه ModelViewSet:

class SingerViewSet(ModelViewSet):
    queryset = Singer.objects.all()
    serializer_class = SingerSerializer
    filter_backends = [FullTextSearchFilter]
    search_config = "portuguese"
    search_fields = ["name"]
    similarity_threshold = 0.3
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

فراخوانی مثال به API با endraw خام `?search=Bruninho` که فقط موارد دارای فیلد را نشان می دهد


منابع

[1] جستجوی تمام متن: پیاده سازی با Postgres و Django

[2] جستجوی کامل متن قدرتمند در PostgreSQL در کمتر از 20 خط

[3] جنگو-جستجوی متن کامل

[4] فیلتر کردن – SearchFilter

[5] rest_framework/filters.py


عکس روی جلد توسط داگلاس لوپس در Unsplash

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا