- 1. PostgreSQL жүйесінде толық мәтінді іздеу
- 2. Үлгіге SearchVectorField және индекс қосу
- 3. Мазмұнның барлық түрлерін іздеуге арналған SearchView сыныбы
- 4. Бейнелеу беті
- 5. "urls.py" файлы
- 6. "SearchViewByContent" сыныбы
- 7. Модельді аудару батареясы арқылы көп тілді қолдау
- 1. Үлгі
- 2. Іздеу көрінісі
- 8. Қорытынды
Ақырында, мен сайттағы сұраныстарды оңтайландыруды ескере отырып, бірнеше модельдер бойынша жеткілікті жылдам толық мәтінді іздеуді жүзеге асыра алдым, бұл маған сәйкес келетін және жобаның сапасына қойылатын талаптарыма жауап береді.
Егер сіз қазір сайтты іздеуді пайдалансаңыз, іздеу өте жылдам жұмыс істейтінін және бірнеше іздеу топтарын қайтаратынын көресіз: Мақалалар, Түсініктемелер, Форум тақырыптары, Форумға жауаптар, Тесттер.
Барлық іздеу топтарынан үш нәтиже таңдалады және әрбір топта табылған нәтижелердің жалпы саны үшін есептегіш болады және қалған нәтижелерді бөлек қойындыларда көру ұсынылады.
Нәтижесінде, мен мұндай бөлуді жасадым, өйткені барлық нәтижелермен бір тізім жасау ресурстар тұрғысынан өте қымбат және жеткілікті тиімді емес.
Сонымен, соңғы нәтиже келесідей көрінеді:
Кейіннен бұл тәсіл іздеу жүйесін икемді және оңай өзгертуге мүмкіндік береді, бұл сайттың басқа бөліктеріне қарамастан іздеуге кез келген мазмұнды қосуға мүмкіндік береді.
Ал енді мен мұны қалай жасағанымды нақты айтып беремін.
PostgreSQL жүйесінде толық мәтінді іздеу
PostgreSQL дерекқоры толық мәтінді іздеуді қолдайды, ал Django ORM оны жүзеге асыруға мүмкіндік береді.
Толық мәтінді іздеуді іске қосудың ең оңай жолы - Django құжаттамасында сипатталғандай үлгі өрісінде іздеу әдісін пайдалану.
Мысалы, осылай
Entry.objects.filter(text__search='Cheese')
Бұл жағдайда Енгізу үлгісінде кірістірілген іздеу функциясының көмегімен толық мәтінді іздеу шақырылатын мәтіндік өріс бар.
Толық мәтінді іздеуді орындаудың жетілдірілген жолы - SearchVector іздеу векторларын бірнеше өрістер бойынша пайдалану. Солай
from django.contrib.postgres.search import SearchVector Entry.objects.annotate(search=SearchVector('text', 'tagline')).filter(search='Cheese')
Өкінішке орай,
аннотация
әдісін қолдану тиімсіз, себебі бұл әдіс кейде көп уақытты алады.
Өнімділікті жақсарту үшін Django құралдары PostgreSQL дерекқорымен индекстелген
SearchVectorField
арнайы өрісін пайдалануды ұсынады. Бұл сайтта іздеуді айтарлықтай жылдамдатуға мүмкіндік береді.
Үлгіге SearchVectorField және индекс қосу
Мысал ретінде мақала үлгісін пайдаланып SearchVectorField қосуды және индекстеуді көрсетемін.
from django.contrib.postgres.indexes import GinIndex from django.contrib.postgres.search import SearchVectorField class Article(models.Model): title = models.CharField('Title', max_length=200) content = models.TextField(verbose_name='Content', blank=True) # SearchVectorField for full-text search search_vector = SearchVectorField(null=True) class Meta: indexes = [GinIndex(fields=["search_vector",]),]
Көріп отырғаныңыздай, ұсынылған кодта Search_vector өрісі бар, ол мақала үлгісінің Meta сыныбында GinIndex спецификациясы арқылы индекстеледі.
SearchVectorField және осы өрісті индекстеуді қосқаннан кейін жаңа тасымалдауларды жасаңыз
python manage.py makemigrations
Жалпы, жаңа көші-қон осылай болады
# Generated by Django 3.2 on 2023-03-27 21:03 import django.contrib.postgres.indexes import django.contrib.postgres.search from django.db import migrations class Migration(migrations.Migration): dependencies = [ # depends on ] operations = [ migrations.AddField( model_name='article', name='search_vector', field=django.contrib.postgres.search.SearchVectorField(null=True), ), migrations.AddIndex( model_name='article', index=django.contrib.postgres.indexes.GinIndex(fields=['search_vector'], name='knowledge_a_search__682520_gin'), ), ]
Бірақ бұл жеткіліксіз болады, өйткені әлі де SearchVectorField өрісін толтыру қажет, оны тасымалдау кезінде де жасауға болады. Сондықтан тасымалдауды келесідей өзгертеміз
# Generated by Django 3.2 on 2023-03-27 21:03 import django.contrib.postgres.indexes import django.contrib.postgres.search from django.db import migrations def compute_search_vector(apps, schema_editor): Article = apps.get_model("knowledge", "Article") Article.objects.update(search_vector=django.contrib.postgres.search.SearchVector("title", "content")) class Migration(migrations.Migration): dependencies = [ # depends on ] operations = [ migrations.AddField( model_name='article', name='search_vector', field=django.contrib.postgres.search.SearchVectorField(null=True), ), migrations.AddIndex( model_name='article', index=django.contrib.postgres.indexes.GinIndex(fields=['search_vector'], name='knowledge_a_search__682520_gin'), ), migrations.RunPython( compute_search_vector, reverse_code=migrations.RunPython.noop ), ]
Бұл код
SearchVectorField
файлын дереу толтыру үшін орындалатын питон кодын қосатын қосымша қадамды, атап айтқанда
compute_search_vector
функциясын іске қосатын
migrations.RunPython
соңғы қадамын қосады.
SearchVectorField
маңызды мүмкіндігі бар, оны
SearchVector
ішіне тікелей қосу мүмкін емес, бірақ оны үлгі менеджерінің
жаңарту
әдісі арқылы толтыруға болады. Сондықтан код осылай көрінеді
Article.objects.update(search_vector=django.contrib.postgres.search.SearchVector("title", "content"))
Содан кейін тасымалдауды іске қосыңыз
python manage.py migrate
Енді барлық мақалаларда индекстелген іздеу өрісі бар. Бірақ іс жүзінде бұл іздеу жүйесінің толыққанды жұмысы үшін жеткіліксіз, өйткені іздеу өрісі жаңа нысанды жасау жағдайында да, ескісін өңдеу жағдайында да толтырылуы керек. Ол үшін Django құжаттамасы PostgreSQL құжаттамасымен кеңесуді және триггерлерді жасауды ұсынады. Бұл жақсы және дұрыс, бірақ қандай да бір себептермен PostgreSQL-ге қайтадан кіріп, барлық қажетті триггерлерді қолмен жасағыңыз келмесе ше. Сонда қалай болу керек? Бұл жағдайда Джангоның сигнал/слот жүйесі бізді құтқарады.
Ол үшін мақала үлгісіне келесі кодты қосыңыз
def update_search_vector(self): qs = Article.objects.filter(pk=self.pk) qs.update(search_vector=SearchVector("title", "content"))
Содан кейін post_save сигналына қосылыңыз.
from django.db.models.signals import post_save from django.dispatch import receiver @receiver(post_save, sender=Article) def post_save_artcile(sender, instance, created, update_fields, **kwargs): instance.update_search_vector()
Осылайша, мақала нысаны сақталған сайын іздеу өрісі жаңартылып отырады.
Мазмұнның барлық түрлерін іздеуге арналған SearchView сыныбы
Ал енді мазмұнның бірнеше түрін іздеуге және қажетті нәтижені беруге мүмкіндік беретін SearchView жазамыз. Біздің жағдайда, біздің сайтта мақалалар («Мақалалар») және пікірлер («Пікірлер») бар делік.
class SearchView(View): template_name = 'search/index.html' def get(self, request, *args, **kwargs): query = request.GET.get('search', None) article_results = Article.objects.filter(search_vector=query) comment_results = Comment.objects.filter(search_vector=query) return render( request=request, template_name=self.template_name, context={ 'search': query or '', 'article_results': article_results[:3], 'article_results_count': article_results.count(), 'comment_results': comment_results[:3], 'comment_results_count': comment_results.count(), } )
get параметрі search іздеу фразасына жауап беретін сайт жолында берілетінін ескеріңіз. Сондай-ақ, дерекқор сұрауларын оңтайландыру үшін бірнеше түзетулер бар.
Django жалқау дерекқор сұрауларын пайдаланатындықтан, сұраулар дәл қажет болғанда орындалады. Осылайша сызық
article_results = Article.objects.filter(search_vector=query)
деректер базасына жалпы сұранысты ғана қояды, бірақ оны орындамайды, өйткені бастапқы іздеу беті үшін бізге табылған материалдардың жалпы саны және барлық табылған материалдардан тек алғашқы үш нысан қажет.
Сондықтан, көрсету контексті ретінде тек екі QuerySets беріледі.
'article_results': article_results[:3], 'article_results_count': article_results.count(),
article_results[:3]
табылған жазбаларда
шектеу
әрекетін орындайды және тек үш нысанды қайтарады
article_results.count()
барлық табылған жазбаларды санайды. Шын мәнінде, мұндай қосымша код сұрауларды орындауға кететін уақытты айтарлықтай қысқартуы мүмкін, бұл сайтта іздеуді айтарлықтай жылдамдатады.
Бейнелеу беті
Әрі қарай бет көрсетіледі. Мен өз бетімнің толық кодын бермеймін, бірақ оның қалай көрінетінін жалпы түрде көрсетемін
Бізде
base.html
жалпы үлгісінен мұраланған негізгі іздеу беті бар (Бұл үлгіні қарастырудың мағынасы жоқ).
Бірақ теңшелетін теголарға келетін болсақ, бұл жерде біз сәл ұзағырақ тоқтаймыз. Мұнда олардың бірнешеуі бар:
- append_query_to_url - белгілі бір мазмұн үшін іздеу бетінің url мекенжайына сұрау параметрін қосуға арналған тег.
- іздеу_өрісі - іздеу өрісін көрсетуге арналған тег
- табылған_нысандар - мазмұн нәтижелерін шығаруға арналған тег
{% extends 'base.html' %} {% block page %} {% load search %} {% url 'search:articles' as articles_search_url %} {% url 'search:comments' as comments_search_url %} {% append_query_to_url articles_search_url as articles_search_url_with_query %} {% append_query_to_url comments_search_url as comments_search_url_with_query %} {% search_field search %} {% block search_result %} {% found_objects article_results article_results_count 'Articles' 'Show all articles' articles_search_url_with_query %} {% found_objects comment_results comment_results_count 'Comments' 'Show all comments' comments_search_url_with_query %} {% endblock %} {% endblock %}
"Үлгілер" каталогы
Кейбір мақалаларда, мысалы, Blocktranslate тегі сияқты қойындылар блогының шаблон тегін қалай жазу керек бөлімінде мен бұл каталогтың құрылымын және нені сипаттадым. үлгі тегтерін тіркеу үшін сол жерде болуы керек, сондықтан мен оған қайта тоқталмаймын.
Сонымен, templatetags/search.py файлының мазмұнын қарастырайық
# -*- coding: utf-8 -*- from django import template from django.template.defaultfilters import urlencode register = template.Library() @register.inclusion_tag('search/search_field.html', takes_context=True) def search_field(context, value, **kwargs): context.update({'query_value': value}) return context @register.simple_tag(takes_context=True) def append_query_to_url(context, url): return '{}?search={}'.format(url, urlencode(context.get('search', ''))) @register.inclusion_tag('search/found_objects.html', takes_context=True) def found_objects(context, results, results_count, objects_title, all_search_message, search_url): context.update({ 'results': results, 'results_count': results_count, 'objects_title': objects_title, 'all_search_message': all_search_message, 'search_url': search_url }) return context
Каталог "үлгілер/іздеу"
Әрі қарай, қосу тег үлгілері дегеніміз не екенін қарастырайық
"search/found_objects.html" файлы
Жалпы алғанда, іздеудегі бірнеше нысандар үшін үлгіні көрсету төмендегідей болады.
Көріп отырғаныңыздай, bootstrap-тен белгілеу мұнда пайдаланылады, сонымен қатар нысанның өзін көрсетудің мысалы жоқ. Егер сіз осы мақаланы оқып жатсаңыз, сізге қажет нәрсені өзіңіз жаза алатыныңызға сенімдімін, өйткені бұл мақала менің тәжірибемнің сипаттамасы болып табылады, бірақ ақылсыз көшіруге тікелей нұсқаулық емес.
{% load i18n %} <div class="card box-shadow m-2"> <div class="card-header">{{ objects_title }} <span class="badge badge-primary">{{ results_count }}</span> </div> {% if results|length > 0 %} {% for object in results %} {# You custom render of object #} {% endfor %} <div class="card-footer border-0"><a href="{{ search_url }}" class="btn btn-sm btn-outline-secondary">{{ all_search_message }}</a></div> {% else %} <div class="card-body"> {% trans 'Nothing found' %} </div> {% endif %} </div>
"search/search_field.html" файлы
<form method="get" class="my-3 px-3"> <div class="input-group bmd-form-group pt-0"> {% load i18n %} <input class="form-control" name="search" placeholder="{% trans 'Search' %}" value="{{ query_value }}" title="" type="text"> <div class="input-group-append"> <button type="submit" class="btn btn-outline-secondary mdi mdi-magnify mdi-0"></button> </div> </div> </form>
"urls.py" файлы
Содан кейін Django маршрутизаторына SearchView қосыңыз.
# -*- coding: utf-8 -*- from django.urls import path from search import views app_name = 'search' urlpatterns = [ path('', views.SearchView.as_view(), name='index'), ]
Django толық мәтінді іздеу бетінің параметрі осылай болады, кез келген жағдайда негізгі іздеу беті осылай көрінеді. Бірақ мен жеке мазмұн бөліктері үшін іздеу беттерін де қостым, бұл да маңызды. Негізгі іздеу бетінде пайдаланушы оның сұрауы бойынша сайттан кем дегенде бірдеңе табуға болатын-болмайтынын көре алады, ал егжей-тегжейлі беттерде пайдаланушы сайтта табылған барлық жазбаларды көре алады.
"SearchViewByContent" сыныбы
Ал енді мазмұнның әртүрлі түрлерінде іздеуге арналған жалпылама класс жазайық. Ол үшін Жалпы класын ListView пайдалануға болады. Іздеу іздеу векторының өрісі бойынша жүргізіледі. Сондай-ақ, дұрыс беттеу үшін бізге беттеу URL мекенжайы қажет. Мұның бәрі осы кодта бар.
class SearchViewByContent(ListView): template_name = 'search/search_objects.html' paginate_by = 10 def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context.update({ 'search': self.request.GET.get('search', None) or '', 'last_question': self.get_pagination_url() }) return context def get_pagination_url(self): return self.request.get_full_path().replace(self.request.path, '') def get_queryset(self): qs = super().get_queryset() query = self.request.GET.get('search', None) return qs.filter(search_vector=query)
Бетті көрсету
Бұрыннан бар үлгінің кодын қолданып, оның әрекетін кеңейтейік. Көріп отырғаныңыздай, бұл жерде django bootstrap 4 батарея үлгісінің тегі қолданылады. Алдыңғы мақалалардың бірінде мен батареяның ескі нұсқасын қалай қосу керектігін сипаттадым. Содан бері ештеңе өзгерген жоқ және мақала django bootstrap 5 соңғы нұсқасына да қатысты.
{% extends 'search/index.html' %} {% block search_result %} <div id="object-list"> {% load bootstrap_pagination from bootstrap4 %} {% for object in object_list %} {# Render your object here #} {% empty %} <div class="card card-body mb-3">{{ not_found_message|default:_("Nothing found") }}</div> {% endfor %} {% if object_list %} <div class="mt-3">{% bootstrap_pagination object_list pages_to_show="3" url=last_question justify_content='center' %}</div> {% endif %} </div> {% endblock %}
"urls.py" файлына маршруттарды қосыңыз
# -*- coding: utf-8 -*- from django.urls import path from search import views from articles.models import Article, Comment app_name = 'earch' urlpatterns = [ path('', views.IndexView.as_view(), name='index'), path('articles/', views.SearchView.as_view(queryset=Article.objects.all()), name='articles'), path('comments/', views.SearchView.as_view(queryset=Comment.objects.all()), name='comments'), ]
Осылайша, сайттағы мазмұнның белгілі бір түрлерін іздеу жүзеге асырылатын болады.
Модельді аудару батареясы арқылы көп тілді қолдау
Django modeltranslation - үлгілеріңізге көптілділікті қосатын тамаша бума, бірақ өкінішке орай ол SearchVectorField өрісімен үйлесімді емес. Мұны істеу үшін сайтта қолдау көрсетілетін әрбір тіл үшін жаңа SearchVectorField өрістерін қолмен қосу керек. Бірақ бұл шынымен де көп жұмыс емес. Және ол осылай көрінуі мүмкін
Үлгі
class Article(models.Model): # Another code # Search vectors search_vector = SearchVectorField(null=True) search_vector_ru = SearchVectorField(null=True) search_vector_en = SearchVectorField(null=True) objects = ArticleManager() class Meta: ordering = ['-pub_date'] verbose_name = _('Article') verbose_name_plural = _('Articles') indexes = [ GinIndex(fields=[ "search_vector", "search_vector_ru", "search_vector_en", ]), ]
Тиісінше, жаңа көшіруді әрбір жаңа өріс SearchVectorField үшін түзету қажет болады.
Іздеу көрінісі
Ал SearchView іздеуді келесідей түзетуге болады
class IndexView(View): template_name = 'evileg_search/index.html' def get(self, request, *args, **kwargs): query = request.GET.get('search', None) current_language = get_language() article_results = Article.objects.filter( Q(**{'search_vector_{}'.format(current_language): query}) | Q(search_vector=query) ) comment_results = Comment.objects.filter(search_vector=query) return render( request=request, template_name=self.template_name, context={ 'search': query or '', 'article_results': article_results[:3], 'article_results_count': article_results.count(), 'comment_results': comment_results[:3], 'comment_results_count': comment_results.count(), } )
Бұл мақалалар үшін бөлек «Көру» үшін де қолданылады. Оны өзіңіз бөлек жазып көріңіз және сұрауды осы Көріністегідей өзгертіңіз.
Бұл жерде де маңызды жайт бар. Мен вектор бойынша тілмен де, тілсіз де іздеймін. Бұл менде белгілі бір мақаланың барлық тілдеріне аудармалары болмауы мүмкін екеніне байланысты және менің ойымша, аударма болмаса да, іздеу нәтижелері әрқашан қайтарылуы керек.
Қорытынды
Осылайша сіз мазмұнның әртүрлі түрлерін қарапайым іздеуді жасай аласыз, ол бәрінен бұрын сайттағы мазмұнның басқа түрлерімен іздеуді болашақта кеңейту үшін айтарлықтай өзгертілетін және қолдау көрсетілетін болады.
Замечательная статья где давольно подробно расписан поиск. Когда то я искал что то именно вот такое. И на самом деле вариантов использования очень и очень много.
Спасибо Евгений за статью.
Заметила, что в русском варианте сложно со stemming поиска. Добавила во view SearchQuery c config, стало отлавиливать (кот - котЫ). Буду признательная за инфу, как этого добиться другим способом.
Пока не подскажу, не вставало такой задачи. Для меня Django/Python - это хобби, а не профессиональная область
В любом случае спасибо за ориентацию. Для моего сайта подойдёт такая логика.
К своему удивлению обнаружила, что украинский язык не включён в postgres-e в full text search. Есть какие-то кустарные варианты с прикручиванием файлов с stopwords и т.д.Я наверное сделаю "наивный" поиск, если выбран украинский.
А вы сталкивались с этой проблемой? Спасибо.
А в чём это выражается? У меня поиск срабатывает по все подключённым языкам, в том числе и по украинскому.
stackoverflow
Я сделала триггеры для update полей и вектора:
Ошибка выпадает на pg_catalog.ukrainian, жалуется, что нет такого.
p.s Сделала менеджер модели, к фильрует поле на украинском (без конфигураций, иначе тоже жалуется).
Может быть пригодится тем, кто использует django-ckeditor-5 for admin + modeltranslation package. Поле для редактирования in models.py нужно прописывать как обычное текстовое. А вот в админ классе обозначать