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

متن 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