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

متن 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
حتی امکان نمایش داده های آن نیز وجود دارد 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__"

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

این را می توان با افزودن پارامتر در سازنده 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

منابع
[1] جستجوی تمام متن: پیاده سازی با Postgres و Django
عکس روی جلد توسط داگلاس لوپس در Unsplash



